Initializing static constexpr variables and classes inside a struct
Solution 1
A static constexpr
member has a value upon its initialization inside the class { }
scope, but it does not have a location in memory (an address) until it is defined outside the class { }
. The reason is that you may decide to include some or all of its specializations in a link library (e.g. .o
or .so
), or whether to give effectively-inline linkage to specializations by default.
The out-of-class definition is required if the address of the object is ever used, which implies that it must exist as a global variable. On the other hand, if you want the constexpr
member only to exist at compile time, prohibiting global storage allocation, then omitting the definition is a good choice.
By the way, it's not allowed to put the constexpr
specifier on a function that can never be evaluated as a constant expression, such as sayhi
which prints to std::cout
. This is a "no diagnostic required (NDR)" rule, meaning that the compiler might not complain now but the next compiler version might.
Solution 2
Since C++ 17 introduced static inline variables and made static constexpr variables implicitly inline, the answer to your question is now much more simple. So in your example, you could simply use the following without any declaration of variable bee
outside the class (although it's still allowed for compatibility reasons):
template<int x>
struct A {
static constexpr b<int> bee = x;
};
Source: cppreference
A static data member may be declared inline. An inline static data member can be defined in the class definition and may specify an initializer. It does not need an out-of-class definition:
struct X { inline static int n = 1; };
If a static data member is declared constexpr, it is implicitly inline and does not need to be redeclared at namespace scope. This redeclaration without an initializer (formerly required [...]) is still permitted, but is deprecated.
Related videos on Youtube
Comments
-
Carlos Miguel Colanta almost 2 years
Here is my working code example:
#include <iostream> template<typename B> class b { public: int y; constexpr b(int x) : y(x) { } constexpr void sayhi() { std::cout << "hi" << std::endl; } }; template<int x> struct A { static constexpr b<int> bee = x; static constexpr int y = x; // this one is fine and usable already, I don't have to do something like what I did on member bee inline static void sayhi() { std::cout << y << std::endl; } }; template<int x> constexpr b<int> A<x>::bee; // why do I have to do something like this, it will cause errors if I don't. int main(int argc, char** argv) { A<30>::bee.sayhi(); // works fine A<30>::sayhi(); // works fine return 0; }
What my code does is simple, I have template struct
A
that has two static variables, namely astatic constexpr int y
and astatic constexpr b<int> bee = x;
. My template structA
will get the value of the argument which will be copied byx
from the template parameter. My question is: how come when it comes to classes, I have to initialize the class by doing something like this:template<int x> constexpr b<int> A<x>::bee;
If I don't use the code above, I get the
undefined
reference error. Wherein the int is already fine and accessible just from doing something like:static constexpr int y = x;
I am concerned why I don't have to forward declare it anymore.
-
Carlos Miguel Colanta over 8 yearsAhhhh so, they aren't allocated right at the start eventhough they are defined?
-
Potatoswatter over 8 years@CarloBrew Technically, it's "declared with an initializer" but not "defined" as an object, according to the official standard terminology. The out-of-class declaration is the definition.
-
Carlos Miguel Colanta over 8 yearsAhhh i see thanks! and what do you mean by evaluated as constexpr? how should i avoid that?
-
Potatoswatter over 8 yearsI didn't say evaluated as constexpr, I said evaluated as a constant expression… and avoid putting
constexpr
on functions that can only be executed at runtime. Theconstexpr
specifier indicates that the function is safe for compile time, at least for some parameter values. -
Carlos Miguel Colanta over 8 yearsOooo because
cout
will only be evaluated at run time? -
M.M almost 8 years"omitting the definition is a good choice" - this causes undefined behaviour (NDR) if the object is inadvertently odr-used
-
Potatoswatter almost 8 years@M.M In practice, that is always a link error. It's reasonable to rely on the linker to tell you when a variable is ODR-used but not defined. The main downside IMHO is that ODR-use is only subtly distinguished from ordinary use, especially when forwarding references are involved. For example, if you're passing an undefined
constexpr
global to atuple
orpair
constructor, the unary+
operator is needed.