C++ Static Polymorphism (Crtp) and Using Typedefs from Derived Classes

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



Leave a reply



Submit