在struct中初始化静态constexpr变量和类

Car*_*nta 11 c++ struct constexpr

这是我的工作代码示例:

#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;
}
Run Code Online (Sandbox Code Playgroud)

我的代码做的很简单,我的模板结构A有两个静态变量,即a static constexpr int y和a static constexpr b<int> bee = x;.我的模板结构A将获取x将从模板参数复制的参数的值.我的问题是:为什么在课程方面,我必须通过这样做来初始化课程:

template<int x>
constexpr b<int> A<x>::bee; 
Run Code Online (Sandbox Code Playgroud)

如果我不使用上面的代码,我会得到undefined引用错误.其中int已经很好了,可以通过以下方式访问:

static constexpr int y = x;    
Run Code Online (Sandbox Code Playgroud)

我担心为什么我不必再向前宣布它了.

Pot*_*ter 9

static constexpr构件具有在其内部初始化的值class { }范围,但它不具有在存储器(一个地址)的位置,直到它被外部的定义class { }.原因是您可能决定在链接库(例如.o.so)中包含其部分或全部特殊化,或者是否默认为特殊化提供有效的内联链接.

如果使用对象的地址,则需要类外定义,这意味着它必须作为全局变量存在.另一方面,如果您希望constexpr成员仅在编译时存在,禁止全局存储分配,则省略该定义是一个不错的选择.

顺便说一句,不允许将说明constexpr符放在一个永远不能被计算为常量表达式的函数上,例如sayhi打印到的函数std::cout.这是一个"无需诊断(NDR)"规则,这意味着编译器现在可能不会抱怨,但下一个编译器版本可能会抱怨.


Gab*_*rin 7

由于 C++ 17 引入了静态内联变量并使静态 constexpr 变量隐式内联,因此您的问题的答案现在要简单得多。bee因此,在您的示例中,您可以简单地使用以下内容,而无需在类外部声明任何变量(尽管出于兼容性原因仍然允许这样做):

template<int x>
struct A {
    static constexpr b<int> bee = x;
};
Run Code Online (Sandbox Code Playgroud)

来源:cppreference

静态数据成员可以声明为内联。内联静态数据成员可以在类定义中定义,并且可以指定初始值设定项。它不需要类外定义:

struct X
{
    inline static int n = 1;
};
Run Code Online (Sandbox Code Playgroud)

如果静态数据成员声明为 constexpr,则它是隐式内联的,不需要在命名空间范围内重新声明。这种不带初始化器的重新声明(以前是必需的[...])仍然是允许的,但已被弃用。