Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Compiler error when using CRTP with static_assert

Consider the following code:

template<typename Derived>
struct Base {
    static constexpr int x_base = Derived::x_derived;
    //static_assert(x_base > 1, "Oops");
};

struct Derived : public Base<Derived> {
    static  constexpr int x_derived = 5 ;
};

Base<Derived> obj;

This compiles fine on gcc but if I uncomment the static_assert line, it complains that

error: incomplete type 'Derived' used in nested name specifier
static constexpr int x_base = Derived::x_derived;

I tried it with different versions of gcc from 4.9 to 5.3 and I get the same error (you can try it on godbolt here). clang refuses to compile it even without the static_assert, and complains that

error: no member named 'x_derived' in 'Derived'
static constexpr int x_base = Derived::x_derived;

Which compiler is correct (if any)? Is there a nice way to fix the code?

like image 668
toth Avatar asked Apr 26 '16 15:04

toth


1 Answers

Accessing nested names requires the class to be complete, but Derived isn't complete here yet:

template<typename Derived>
struct Base {
    static constexpr int x_base = Derived::x_derived;
                                  ^^^^^^^^^
};

so the code is ill-formed.

There are a few workarounds. First, you can separately just pass in the value as a template argument:

template <typename Derived, int x_derived>
struct Base {
    static constexpr int x_base = x_derived;
};

struct Derived : public Base<Derived, 5> { };

Second, if possible (e.g. you don't need x_derived to declare any members), you can move the value into a function to delay its instantiation:

template<typename Derived>
struct Base {
    static constexpr int x_base() {
        static_assert(Derived::x_derived > 1, "Oops");
        return Derived::x_derived;
    }

};

struct Derived : public Base<Derived> {
    static constexpr int x_derived = 5;
};
like image 95
Barry Avatar answered Nov 19 '22 09:11

Barry