C++ static polymorphism (CRTP) and using typedefs from derived classes
derived
is incomplete when you use it as a template argument to base
in its base classes list.
A common workaround is to use a traits class template. Here's your example, traitsified. This shows how you can use both types and functions from the derived class through the traits.
// Declare a base_traits traits class template:
template <typename derived_t>
struct base_traits;
// Define the base class that uses the traits:
template <typename derived_t>
struct base {
typedef typename base_traits<derived_t>::value_type value_type;
value_type base_foo() {
return base_traits<derived_t>::call_foo(static_cast<derived_t*>(this));
}
};
// Define the derived class; it can use the traits too:
template <typename T>
struct derived : base<derived<T> > {
typedef typename base_traits<derived>::value_type value_type;
value_type derived_foo() {
return value_type();
}
};
// Declare and define a base_traits specialization for derived:
template <typename T>
struct base_traits<derived<T> > {
typedef T value_type;
static value_type call_foo(derived<T>* x) {
return x->derived_foo();
}
};
You just need to specialize base_traits
for any types that you use for the template argument derived_t
of base
and make sure that each specialization provides all of the members that base
requires.
CRTP: Pass types from derived class to base class
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.
variadic CRTP with a typedef
With CRTP, Base
class is still incomplete inside Feature1
definition.
So you cannot use alias defined inside it.
As workaround you may create traits.
template <typename T>
struct MyTrait;
template <class Base>
class Feature1 {
public:
using value_type = typename MyTrait<Base>::type;
public:
void extraMethod1() {
auto base = static_cast<Base&>(*this);
base.basicMethod();
}
};
template <class T, template <typename> class ... Skills>
class X;
template <class T, template <typename> class ... Skills>
struct MyTrait<X<T, Skills>>
{
using type = T;
};
template <class T, template <typename> class ... Skills>
class X : public Skills<X<T, Skills...>>... {
public:
using value_type = typename MyTrait<X>::type;
public:
void basicMethod() {}
};
CRTP refer to typedef in derived class from base class
The problem is at this point
template <typename T>
class A
{
typedef typename T::Type MyType;
^^^
};
T
needs to be a complete type. But in your case, when A<T>
is instantiated here:
template <typename T>
class B : public A<B<T>>
^^^^^^^
B<T>
is not yet a complete type! So this cannot work unfortunately.
The simple solution is just to pass in Type
separately:
template <typename T, typename Type>
class A
{
typedef Type MyType;
};
template <typename T>
class B : public A<B<T>, T>
{
};
Static Polymorphism with CRTP: Using the Base Class to Call Derived Methods
Well, you need to declare print a template function :
template<class T>
void Print(Base<T>& Object)
{
std::cout << Object.ToStringInterface() << std::endl;
}
c++ static polymorphism (CRTP) Resulting in incomplete type when evaluating a `static constexpr`
Per @David, the problem has to do with Face
being incomplete, because it goes into Base
before finishing the definition of Face
.
However, the solution linked to by @David is a bit old and misses some tricks of which we can take advantage. Namely, @m.s. shows us that static constexpr
functions are fine - and also based on my own experimentation - and we really only need to deal with this particular case of static constexpr
variables, and perhaps types accessed from Derived
.
The following (live example) shows how to solve this problem, while keeping each class encapsulated to it's own h-file, making it somewhat cleaner:
#include <iostream>
// Begin: h-file of Base
template<class Drvd>
class ConstValue;
template<class Drvd>
class Base
{
public:
static constexpr bool val = ConstValue<Drvd>::val;
};
// End: h-file of Base
// Begin: h-file of Derived
class Derived;
template<>
class ConstValue<Derived>
{
public:
static constexpr bool val = true;
};
class Derived : public Base<Derived>
{
friend class Base<Derived>;
private:
static constexpr bool val = true; // optional
};
// End: h-file of Derived
// Main
int main()
{
std::cout << Derived::Base::val << std::endl;
}
The general idea is that for each constexpr
that Base
needs to access from Derived
, we can create a single class that encapsulate the variables, and then overload the class for each Derived
that uses Base
.
Using typedefs of a non-template derived class in the base class when using CRTP
base_traits<derived>
must be declared and defined prior it's usage since it is needed for the implicit instancation of base<derived>
(below, I forward declared derived
) :
template <class D>
struct base_traits;
template <class D>
struct base
{
typedef typename base_traits<D>::value_t value_t;
};
struct derived;
template<>
struct base_traits<derived>
{
typedef int value_t;
};
struct derived : base<derived>
{
typedef base_traits<derived>::value_t value_t;
};
int main(void)
{
derived d;
}
Live demo
Use of member of derived type in CRTP class
Or do I have to force C to be defined outside of B?
Yes, unfortunately you have to do this. Usually you can define a template class before A
and specialize it for B
, containing the C
type. This allows you to use it in A
.
template<typename T>
struct members;
template<class Derived>
class A {
typedef typename members<Derived>::C D;
D x;
};
template<>
struct members<class B> {
class C { };
};
class B : public A<B> {
public:
};
Related Topics
Can You Remove Elements from a Std::List While Iterating Through It
How to Determine If a String Is a Number With C++
Combining C++ and C - How Does #Ifdef _Cplusplus Work
C++11 Return Value Optimization or Move
Floating Point Division VS Floating Point Multiplication
C++11 Does Not Deduce Type When Std::Function or Lambda Functions Are Involved
How to Make a Http Request With C++
How to Call a Parent Class Function from Derived Class Function
Position of Least Significant Bit That Is Set
Operator Precedence VS Order of Evaluation
What Happens If I Assign a Negative Value to an Unsigned Variable
How Does the Import Library Work - Details
Unmangling the Result of Std::Type_Info::Name
What Is a Converting Constructor in C++ ? What Is It For
Gcc Optimization Flag -O3 Makes Code Slower Than -O2
Why Does C++ Require a Cast For Malloc() But C Doesn'T
Can Main Function Call Itself in C++
Why Do I Get an Infinite Loop If I Enter a Letter Rather Than a Number