5

I have a template base class, whereby subclasses are expected to pass themselves as the template parameter.

It looks a little like this:

template<typename T>
struct Base {
    constexpr Base(int x) : m_x(x) {}
private:
    int m_x;
};

struct Derived : public Base<Derived>
{
    static const Derived LIFE;
    constexpr Derived(int x) : Base(x) {}
};

const Derived Derived::LIFE = Derived(42);

That compiles and works as expected. But now I'd like to make Derived::LIFE a constexpr. Is this even possible?

I can't just change it's const qualifier to constexpr, as a constexpr needs to be initialized in its declaration:

test.cpp:10:28: error: constexpr static data member ‘LIFE’ must have an initializer
   static constexpr Derived LIFE;

I can't initialize it there since Derived is an incomplete type:

test.cpp:10:45: error: invalid use of incomplete type ‘struct Derived’
   static constexpr Derived LIFE = Derived(42);

I realize this problem would go away if Derived were a complete type, but I'm pretty attached to the self-referential templated base class in this particular case for reasons not relevant to this question.

If I understand the last paragraph in this answer correctly, it sounds like there is at least some discussion about changing the way incomplete types are handled at some point in the future, but that doesn't help me now.

Is anyone aware of some sort of trick to defer the initialization of LIFE in my above code?

Community
  • 1
  • 1
Martin
  • 3,703
  • 2
  • 21
  • 43
  • *"I have a template base class, whereby subclasses are expected to pass themselves as the template parameter."* It's called the [Curiously Recurring Template Pattern](http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern). – dyp Jan 08 '14 at 23:45
  • Thanks @dyp: I've always wanted a name for the pattern. So I suppose the obvious (but ugly) solution is to move the constant out of its class. I suspect its not practical to reorganize things such that the types are complete in my case. – Martin Jan 08 '14 at 23:57
  • 1
    Hmm after reading the linked answer and the Standard, I still think my second (now removed) comment was correct: As `Derived` is an incomplete type within the "scope" of `Derived` (the name lookup is postponed until after the class definition), you may not create a `constexpr` object of its type as a static data member. However, the definitions of member functions of a class are not in its scope. So you should be able to use something like `static constexpr Derived get_LIFE() { return {42}; }` – dyp Jan 09 '14 at 00:01

2 Answers2

10

You can simply add constexpr to the definition of LIFE:

constexpr Derived Derived::LIFE = Derived(42);

Until recently GCC had a bug where it rejected this; you'll need to use either Clang or GCC 4.9.

Richard Smith
  • 13,696
  • 56
  • 78
  • So can you put `constexpr` on a definition even if it was not part of the declaration? That looks interesting, where is that specified? – Johannes Schaub - litb Feb 09 '14 at 12:36
  • @litb There's no rule saying you can't. 7.1.5/1 says "The `constexpr` specifier shall be applied only to the definition of a variable [...]. If any declaration of a function or function template has a `constexpr` specifier, then all its declarations shall contain the `constexpr` specifier." So a variable declaration cannot have the specifier, but variable redeclarations (unlike functions) do not need to have matching `constexpr` specifiers. – Richard Smith Apr 29 '14 at 22:12
  • 1
    I tested this with GCC ([ideone](https://ideone.com/r8pHW5)), clang ([rextester](http://rextester.com/SBC68067)), and MSVC 2017, and while IntelliSense doesn't much care for it, this works exactly as expected on all three compilers. I accessed the `static constexpr` members by passing them as the size of an `std::array`, to ensure that they are being accessed as compile-time constant expressions. – monkey0506 May 11 '18 at 14:55
  • This do not work with ICC: https://gcc.godbolt.org/z/aquzGx but still +1 – Yankes Apr 08 '20 at 09:42
  • > There's no rule saying you can't. That reasoning makes me uneasy, because if there's also no rule specifying what an implementation must do in that case, isn't it Undefined Behavior? Since @RichardSmith said it's a bug, I'm guessing it actually is defined somewhere. – aij Sep 15 '22 at 15:06
4

I think you shall use lazy initialization. Actually Derived is still incomplete type; because the compiler don't know its size yet.

So the code shall be:

struct Derived : public Base<Derived>
{
    constexpr Derived(int x) : Base(x) {}

    static constexpr Derived getLIFE()
    {
        return Derived(34);
    }
};

EDIT: Same incomplete type behavior can be reproduced using this snippet:

struct MyStruct
{
    static constexpr int x = sizeof(MyStruct);
};
Yousf
  • 3,957
  • 3
  • 27
  • 37
  • Ahh, yes - that works well. Big thanks to both you and @dyp. – Martin Jan 09 '14 at 00:27
  • I had this idea, too, but it does something different from the OP's approach, i.e. it produces a *new* object every time it's invoked. Then again, I suppose when you have a constant expression, the notion of object identity becomes a little pointless. – Kerrek SB Jan 09 '14 at 08:35