bremen_matt
bremen_matt

Reputation: 7369

CRTP traits only working with templated derived class

I have seen an idiom for using Derived type traits in the base class of a CRTP pattern that looks like this:

template<typename Derived>
struct traits;

template<typename Derived>
struct Base {
    using size_type = typename traits<Derived>::size_type;
};

template <typename T>
struct Derived1 : Base<Derived1<T>>{
    using size_type = size_t;
    void print(){ std::cout << "Derived1" << std::endl; }
};

template <typename T>
struct traits<Derived1<T>> {
    using size_type = size_t;
};

int main()
{
    using T = float;
    Derived1<T> d1;
    d1.print();
}

My understanding is that the purpose of the idiom is to delay the instantiation of the Base class's size_type. What I am confused by is the fact that this pattern only seems to work if the derived class is itself templated. For instance, if we change the code to:

template<typename Derived>
struct traits;

template<typename Derived>
struct Base {
    using size_type = typename traits<Derived>::size_type;
};

struct Derived1 : Base<Derived1>{
    using size_type = size_t;
    void print(){ std::cout << "Derived1" << std::endl; }
};

template <>
struct traits<Derived1> {
    using size_type = size_t;
};

int main()
{
    Derived1 d1;
    d1.print();
}

then we get the error

prog.cc: In instantiation of 'struct Base<Derived1>':
prog.cc:21:19:   required from here
prog.cc:18:58: error: invalid use of incomplete type 'struct traits<Derived1>'
     using size_type = typename traits<Derived>::size_type;
                                                          ^
prog.cc:14:8: note: declaration of 'struct traits<Derived1>'
 struct traits;
        ^~~~~~
prog.cc: In function 'int main()':
prog.cc:33:9: error: 'Derived1' is not a template
         Derived1<float> d1;

Could somebody give me an explanation indicating why the templated derived class compiles, but the untemplated class does not?

Upvotes: 0

Views: 87

Answers (2)

aep
aep

Reputation: 1675

The issue you're seeing has nothing to do with CRTP.

Here's what the standard mentions.

If a class template has been declared, but not defined, at the point of instantiation (13.7.4.1), the instantiation yields an incomplete class type (6.7). [Example:

template<class T> class X; X<char> ch; // error: incomplete type
X<char>

Your traits has only been declared at the point of instantiation of Base<Derived>, hence as per the standard(see above extraction from the standard), struct traits<Derived> yields an incomplete type.

You should reorder the code so that it sees the traits<Derived> specialization when Base<Derived> gets instantiated.

Upvotes: 1

Daniel Duvilanski
Daniel Duvilanski

Reputation: 141

The compilation error you are seeing has nothing to do with CRTP, it's just a bit of a mish-mash of dependencies.

In the code without the templation, your "Base" struct needs the definition of the specialized "traits" struct but it only appears afterwards, so it tries to use the incomplete type it saw in the declaration above.

To get the code to work you need to have the "traits" specialization before the Base declaration, which requires you to also add a declaration of Derived 1, here is a compiling code:

class Derived1;

template<typename Derived>
struct traits;

template <>
struct traits<Derived1> {
    using size_type = size_t;
};

template<typename Derived>
struct Base {
    using size_type = typename traits<Derived>::size_type;
};

struct Derived1 : Base<Derived1>{
    using size_type = size_t;
    void print(){ std::cout << "Derived1" << std::endl; }
};


int main()
{
    Derived1 d1;
    d1.print();
}

Upvotes: 1

Related Questions