Initializing static constexpr variables and classes inside a struct

18,770

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.

Share:
18,770

Related videos on Youtube

Carlos Miguel Colanta
Author by

Carlos Miguel Colanta

Aspiring C++ programmer.

Updated on June 04, 2022

Comments

  • Carlos Miguel Colanta
    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 a static constexpr int y and a static constexpr b<int> bee = x;. My template struct A will get the value of the argument which will be copied by x 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
    Carlos Miguel Colanta over 8 years
    Ahhhh so, they aren't allocated right at the start eventhough they are defined?
  • Potatoswatter
    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
    Carlos Miguel Colanta over 8 years
    Ahhh i see thanks! and what do you mean by evaluated as constexpr? how should i avoid that?
  • Potatoswatter
    Potatoswatter over 8 years
    I 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. The constexpr specifier indicates that the function is safe for compile time, at least for some parameter values.
  • Carlos Miguel Colanta
    Carlos Miguel Colanta over 8 years
    Oooo because cout will only be evaluated at run time?
  • M.M
    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
    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 a tuple or pair constructor, the unary + operator is needed.