Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does only one of these CRTP patterns compile?

Tags:

c++

auto

crtp

Consider the following two pieces of code with a CRTP pattern:

template <typename Derived>
struct Base1 {
    int baz(typename Derived::value_type) { 
        return 42; 
    }
};

struct Foo1 : Base1<Foo1> {
    using value_type = int;
};
template <typename Derived>
struct Base2 {
    auto baz() {
        return typename Derived::value_type {};
    }
};

struct Foo2 : Base2<Foo2> {
    using value_type = int;
};

The first one fails to compile while the second compiles. My intuition says that they should both either compile or both not compile. Now, if we replace the auto in Base2 with the explicit type:

template <typename Derived>
struct Base3 {
    typename Derived::value_type baz() {
        return typename Derived::value_type {};
    }
};

struct Foo3 : Base3<Foo3> {
    using value_type = int;
};

it no longer compiles; but I don't really see what's the big difference here. What's going on?


Note: This came up in David S. Hollman's lightning talk, Thoughts on Curiously Recurring Template Pattern, in C++-Now 2019.

like image 994
einpoklum Avatar asked Aug 27 '19 15:08

einpoklum


People also ask

What is the purpose of CRTP?

CRTP may be used to implement "compile-time polymorphism", when a base class exposes an interface, and derived classes implement such interface.

What is CRTP in c++?

The curiously recurring template pattern (CRTP) is an idiom, originally in C++, in which a class X derives from a class template instantiation using X itself as a template argument. More generally it is known as F-bound polymorphism, and it is a form of F-bounded quantification.


1 Answers

The type Foo1 is complete only at the end of };

struct Foo1 : Base1<Foo1> {
    // still incomplete
} /* now complete */;

But before Foo1 start to become defined, it must first instantiate the base class for the base class to be complete.

template <typename Derived>
struct Base1 {
    // Here, no type are complete yet

    // function declaration using a member of incomplete type
    int baz(typename Derived::value_type) { 
        return 42; 
    }
};

Inside the base class body, no class are complete yet. You cannot use nested typename there. The declaration must all be valid when defining a class type.

Inside the body of member function, it's different.

Just like this code don't work:

struct S {
    void f(G) {}
    using G = int;
};

But this one is okay:

struct S {
    void f() { G g; }
    using G = int;
};

Inside the body of member functions, all types are considered complete.

So... why does the auto return type works if it deduce to the type you cannot access?

auto return type is indeed special, since it allows function with deduced return types to be forward declared, like this:

auto foo();

// later

auto foo() {
    return 0;
}

So the deduction of auto can be used to defer usage of types in the declaration that would be otherwise incomplete.

If auto was deduced instantaneously, types in the body of the function would not be complete as the specification imply, since it would have to instantiate the body of the function when defining the type.


As for parameter types, they are also part of the declaration of the function, so the derived class is still incomplete.

Although you cannot use the incomplete types, you can check if the deduced parameter type is really typename Derived::value_type.

Even though the instantiated function recieve typename Derived::value_type (when called with the right set of argument), it is only defined at the instantiation point. And at that point, the types are complete.

There's something analoguous to the auto return type but for parameter and that means a template:

template<typename T>
int baz(T) {
    static_assert(std::is_same_v<typename Derived::value_type, T>) 
    return 42; 
}

As long as you don't directly use the name from the incomplete type inside declarations, you'll be okay. You can use indirections such as templates or deduced return types and that will make the compiler happy.

like image 152
Guillaume Racicot Avatar answered Sep 29 '22 09:09

Guillaume Racicot