无法在 C++17 之前的模式下使用 static constexpr 进行编译
Can't compile with static constexpr under pre-C++17 mode
为什么下面的最小示例不使用 c++11
或 c++14
编译,而是使用 c++17
和 c++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 代码中定义常量,但您可以使用此技巧来调整您无法控制的代码。
为什么下面的最小示例不使用 c++11
或 c++14
编译,而是使用 c++17
和 c++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 memberor 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 implicitlyinline
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 代码中定义常量,但您可以使用此技巧来调整您无法控制的代码。