Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When does a static constexpr class member need an out-of-class definition?

I have the following C++11 code (simplified version):

struct Info
{
    const char * name;
    int version;
};

class Base
{
public:
    const Info info;
    Base (Info info) : info (info) {}
};

class Derived : public Base
{
public:
    static constexpr Info info = {"Foobar", 2};
    Derived () : Base (info) {}
};

int main ()
{
    static Derived derived;
    return 0;
}

GCC 4.9.1 compiles and links this code fine. Clang 3.5.0, on the other hand, complains about an undefined reference:

/tmp/test-109c5c.o: In function `main':
test.cc:(.text+0x1c): undefined reference to `Derived::info'
test.cc:(.text+0x22): undefined reference to `Derived::info'
clang: error: linker command failed with exit code 1 (use -v to see invocation)

Which is right? Is this code legal or not? My understanding of the rules regarding static constexpr members (based mostly on this question) is that an out-of-class definition is needed only when the address of the variable is taken. But I'm not taking the address of Derived::info or using a reference to it anywhere; I'm only passing it by value to the Base constructor.

Various workarounds that I've found:

  • Make both constructors (Base and Derived) constexpr. This may or may not be an option with the real classes, which are more complex than the ones in the example. I'm going to try it, anyway.
  • Declare the instance of Derived in main with automatic rather than static duration. This is not an option for the real project: the Derived class is a plugin implementation, and an instance of it needs to be exported as a public symbol in a shared object.
  • Remove Derived::info entirely and call the Base constructor with a brace-initialized temporary object instead, i.e. Base ({"Foobar", 2}). This solution would work, but it gets ugly (in my opinion) as more members are added to struct Info.
like image 702
John Lindgren Avatar asked Sep 27 '14 18:09

John Lindgren


People also ask

Is static needed for constexpr?

A static constexpr variable has to be set at compilation, because its lifetime is the the whole program. Without the static keyword, the compiler isn't bound to set the value at compilation, and could decide to set it later. So, what does constexpr mean?

Does constexpr take memory?

The alternatives don't have the all of the positives of static constexpr - you're guaranteed compile time processing, type safety, and (potentially) lower usage of memory (constexpr variables don't need to take up memory, they are effectively hard coded unless if possible).

Can you take the address of a constexpr?

If you need to take the address of constexpr variable, declare it as a static member variable. It can be used as a constant expression this way (as opposed to using a function returning a const).


1 Answers

Aha, it seems that the problem is the implicit Info(const Info &) copy constructor. To pass the const Info & reference to that constructor, it's necessary to take the address of Derived::info.

Apparently GCC is more aggressive than Clang in optimizing away the copy constructor. If I use -fno-elide-constructors, then GCC also complains about an undefined reference to Derived::info.

In any case, declaring the Base and Derived constructors as constexpr seems to accomplish what I want to happen here, which is to have Base::info initialized at compile time, rather than copied from a separate Derived::info at run time.

like image 58
John Lindgren Avatar answered Nov 15 '22 01:11

John Lindgren