无法在 C++17 之前的模式下使用 static constexpr 进行编译

Can't compile with static constexpr under pre-C++17 mode

为什么下面的最小示例不使用 c++11c++14 编译,而是使用 c++17c++2a 编译?

#include <iostream>
#include <limits>
#include <vector>

// works:
// static constexpr int VALUE_LIMIT_A = std::numeric_limits<int>::max();

class Classy {
    // does not work in c++11 (constexpr introduced) nor c++14:
    // works if c++17 or newer:
    static constexpr int VALUE_LIMIT_A = std::numeric_limits<int>::max();
    int VALUE_LIMIT_B = std::numeric_limits<int>::max();

    public:
        explicit Classy();
        std::vector<int> classy;
};

Classy::Classy() {
    // does not work:
    classy.resize(3, VALUE_LIMIT_A);

    // works:
    // classy.resize(3, std::numeric_limits<int>::max());

    // works:
    // std::cout << VALUE_LIMIT_A;

    // works:
    // classy.resize(3, VALUE_LIMIT_B);
}

// required in c++11 and c++14
// constexpr int Classy::VALUE_LIMIT_A;

int main() {
    Classy classy{};

    for (const auto& elem : classy.classy) {
        std::cout << elem << ",";
    }
    std::cout << "\n";
}

这是 c++11 的输出:

$ g++ -std=c++11 main.cpp && ./a.out
/tmp/ccon7pPo.o: In function `Classy::Classy()':
main.cpp:(.text+0x31): undefined reference to `Classy::VALUE_LIMIT_A'
collect2: error: ld returned 1 exit status

这是 c++17 的输出:

$ g++ -std=c++17 main.cpp && ./a.out
2147483647,2147483647,2147483647,

因为从 C++17 开始,不再需要在命名空间范围内定义 constexpr static data member

If a const non-inline (since C++17) static data member or a constexpr static data member (since C++11) is odr-used, a definition at namespace scope is still required, but it cannot have an initializer. This definition is deprecated for constexpr data members (since C++17).

struct X {
    static const int n = 1;
    static constexpr int m = 4;
};
const int *p = &X::n, *q = &X::m; // X::n and X::m are odr-used
const int X::n;             // … so a definition is necessary
constexpr int X::m;         // … (except for X::m in C++17)

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 as shown above) is still permitted, but is deprecated. (since C++17)

并注意 std::vector::resize takes the 2nd parameter by reference; which cause VALUE_LIMIT_A to be odr-used 对应 classy.resize(3, VALUE_LIMIT_A);

自 C++17 起,随着内联变量的引入,static constexpr 数据成员是隐式内联变量:

[dcl.constexpr]

1 ... A function or static data member declared with the constexpr specifier is implicitly an inline function or variable ([dcl.inline])...

内联变量和内联函数一样,在使用它们的每个翻译单元中定义。编译器将多个定义解析为一个。这意味着,与 C++14 不同,没有必要为了 ODR 而显式地为 static constexpr 变量提供超出 class 的定义,编译器会处理它。

您也可以在 C++14 中摆脱它。就像另一个答案提到的那样,ODR-uses 是静态数据成员 resize。不过,您可以解决它:

classy.resize(3, int(VALUE_LIMIT_A));

虽然看起来多余,但实际上与直接使用常量有不同的行为。这将创建一个临时整数,其 value 为常量。但它不是 ODR-use 常量。临时对象绑定到引用,因此避免了该问题。虽然最好在 pre-C++17 代码中定义常量,但您可以使用此技巧来调整您无法控制的代码。