CRTP: Pass types from derived class to base class

Viewed 264

In CRTP, the base class can use functions and variables from derived class. However, the types from derived class cannot be used directly by base class, see code below:

#include <iostream>

template <class Derived>
class A {
public:
    //using Scalar = typename Derived::Scalar; // Error!
    static constexpr int NA1 = Derived::NB1;
    static constexpr int NA2 = Derived::NB2;
    static constexpr int NA3 = Derived::NB3;
};

template <int _N = 2>
class B : public A<B<_N>> {
public:
    using Scalar = double;
    static constexpr int NB1 = 1;
    static constexpr int NB2 = _N;
    static constexpr int NB3 { sizeof(Scalar) };
};

int main(int argc, char** argv)
{
    using Type = B<2>;
    std::cout << Type::NA1 << ' '
              << Type::NA2 << ' '
              << Type::NA3 << '\n';
}

// output:
// 1 2 8

If the line using Scalar = typename Derived::Scalar; is uncommented, an error occurs:

main.cpp:6:11: error: invalid use of incomplete type 'class B<2>'

I know that the type (Scalar) can be passed to base class as a template parameter, but why can't it be used just like the variables? Is it just a language rule? Or is there any logical restriction that makes this unable to implement?

1 Answers

Inside A, B is an incomplete type - a compiler hasn't yet seen the complete declaration of B, so you can't use Scalar inside the declaration of A. This is a natural restriction.

The difference between a type and a scalar in your example arises because instantiation of initialization of NA happens not at the point of declaration, but only after B was seen by a compiler (and became a complete type).

Let's change the code and force a compiler to use NA value inside the class declaration:

template <class Derived>
class A {
public:
    static constexpr int NA1 = Derived::NB1;

    std::array<int, NA1> foo();
};

Now you'll get essentially the same error:

<source>:8:41: error: incomplete type 'B<2>' used in nested name specifier
    8 |     static constexpr int NA1 = Derived::NB1;
      |                                         ^~~

This is similar to member functions: you can't use a CRTP base type in their declarations, but you can use that type in their bodies:

void foo() {
    std::array<int, NA1> arr;
    // ...
}

will compile because instantiation happens at the point where a base class is already a complete type.

Related