C++ 对静态 constexpr char[] 的未定义引用
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/8016780/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Undefined reference to static constexpr char[]
提问by Pubby
I want to have a static const
char
array in my class. GCC complained and told me I should use constexpr
, although now it's telling me it's an undefined reference. If I make the array a non-member then it compiles. What is going on?
我想static const
char
在我的班级中有一个数组。GCC 抱怨并告诉我应该使用constexpr
,尽管现在它告诉我这是一个未定义的引用。如果我将数组设为非成员,则它会编译。到底是怎么回事?
// .hpp
struct foo {
void bar();
static constexpr char baz[] = "quz";
};
// .cpp
void foo::bar() {
std::string str(baz); // undefined reference to baz
}
回答by Kerrek SB
Add to your cpp file:
添加到您的 cpp 文件:
constexpr char foo::baz[];
Reason: You have to provide the definitionof the static member as well as the declaration. The declaration and the initializer go inside the class definition, but the member definition has to be separate.
原因:您必须提供静态成员的定义以及声明。声明和初始化程序在类定义中,但成员定义必须是分开的。
回答by Shafik Yaghmour
C++17 introduces inline variables
C++17 引入了内联变量
C++17 fixes this problem for constexpr static
member variables requiring an out-of-line definition if it was odr-used. See the second half of this answer for pre-C++17 details.
C++17 修复了constexpr static
需要外部定义的成员变量的这个问题,如果它被 odr 使用的话。有关 C++17 之前的详细信息,请参阅此答案的后半部分。
Proposal P0386 Inline Variablesintroduces the ability to apply the inline
specifierto variables. In particular to this case constexpr
implies inline
for static member variables. The proposal says:
提案P0386 内联变量引入了将说明inline
符应用于变量的能力。特别是这种情况constexpr
意味着inline
静态成员变量。提案说:
The inline specifier can be applied to variables as well as to functions. A variable declared inline has the same semantics as a function declared inline: it can be defined, identically, in multiple translation units, must be defined in every translation unit in which it is odr--used, and the behavior of the program is as if there is exactly one variable.
内联说明符可以应用于变量以及函数。内联声明的变量与内联声明的函数具有相同的语义:它可以在多个翻译单元中以相同的方式定义,必须在使用它的每个翻译单元中定义,并且程序的行为如下如果只有一个变量。
and modified [basic.def]p2:
并修改了 [basic.def]p2:
A declaration is a definition unless
...
- it declares a static data member outside a class definition and the variable was defined within the class with the constexpr specifier (this usage is deprecated; see [depr.static_constexpr]),
...
声明就是定义,除非
……
- 它在类定义之外声明了一个静态数据成员,并且在类中使用 constexpr 说明符定义了变量(不推荐使用这种用法;请参阅 [depr.static_constexpr]),
...
and add [depr.static_constexpr]:
For compatibility with prior C++ International Standards, a constexpr static data member may be redundantly redeclared outside the class with no initializer. This usage is deprecated. [?Example:
struct A { static constexpr int n = 5; // definition (declaration in C++ 2014) }; constexpr int A::n; // redundant declaration (definition in C++ 2014)
?—?end example?]
为了与之前的 C++ 国际标准兼容, constexpr 静态数据成员可以在没有初始化程序的类外冗余重新声明。此用法已弃用。[?例子:
struct A { static constexpr int n = 5; // definition (declaration in C++ 2014) }; constexpr int A::n; // redundant declaration (definition in C++ 2014)
?—?结束示例?]
C++14 and earlier
C++14 及更早版本
In C++03, we were only allowed to provide in-class initializers for const integralsor const enumeration types, in C++11 using constexpr
this was extended to literal types.
在 C++03 中,我们只被允许为const 整数或const 枚举类型提供类内初始值设定项,在 C++11 中使用constexpr
它被扩展到文字类型。
In C++11, we do not need to provide a namespace scope definition for a static constexpr
member if it is not odr-used, we can see this from the draft C++11 standard section 9.4.2
[class.static.data]which says (emphasis mine going forward):
在 C++11 中,constexpr
如果静态成员不是odr-used,我们不需要为静态成员提供命名空间范围定义,我们可以从 C++11 标准部分草案9.4.2
[class.static.data]中看到这一点,它说(强调我的未来):
[...]A static data member of literal type can be declared in the class definition with the constexpr specifier; if so, its declaration shall specify a brace-or-equal-initializer in which every initializer-clause that is an assignment-expression is a constant expression. [ Note: In both these cases, the member may appear in constant expressions. —end note ] The member shall still be defined in a namespace scope if it is odr-used (3.2)in the program and the namespace scope definition shall not contain an initializer.
[...]可以在类定义中使用 constexpr 说明符声明文字类型的静态数据成员;如果是这样,它的声明应指定一个大括号或等号初始化器,其中每个作为赋值表达式的初始化器子句都是一个常量表达式。[ 注意:在这两种情况下,成员都可能出现在常量表达式中。—end note ] 如果在程序中使用 odr (3.2)并且命名空间范围定义不应包含初始化程序,则该成员仍应在命名空间范围中定义。
So then the question becomes, is baz
odr-usedhere:
那么问题就变成了,这里baz
使用的是odr:
std::string str(baz);
and the answer is yes, and so we require a namespace scope definition as well.
答案是yes,所以我们也需要一个命名空间范围定义。
So how do we determine if a variable is odr-used? The original C++11 wording in section 3.2
[basic.def.odr]says:
那么我们如何确定一个变量是否是odr-used呢?3.2
[basic.def.odr]部分中的原始 C++11 措辞说:
An expression is potentially evaluated unless it is an unevaluated operand (Clause 5) or a subexpression thereof. A variable whose name appears as a potentially-evaluated expression is odr-used unlessit is an object that satisfies the requirements for appearing in a constant expression(5.19) and the lvalue-to-rvalue conversion (4.1) is immediately applied.
一个表达式可能会被求值,除非它是一个未求值的操作数(第 5 条)或其子表达式。名称显示为潜在求值表达式的变量是 odr 使用的,除非它是满足出现在常量表达式(5.19) 中的要求并且立即应用左值到右值转换 (4.1) 的对象。
So baz
does yield a constant expressionbut the lvalue-to-rvalueconversion is not immediately applied since it is not applicable due to baz
being an array. This is covered in section 4.1
[conv.lval]which says :
所以baz
确实会产生一个常量表达式,但左值到右值的转换不会立即应用,因为它是baz
一个数组而不适用。这在4.1
[conv.lval]部分有介绍:
A glvalue (3.10) of a non-function, non-array type Tcan be converted to a prvalue.53 [...]
非函数、非数组类型 T的泛左值 (3.10)可以转换为纯右值。 53 [...]
What is applied in the array-to-pointer conversion.
在数组到指针的转换中应用了什么。
This wording of [basic.def.odr]was changed due to Defect Report 712since some cases were not covered by this wording but these changes do not change the results for this case.
[basic.def.odr] 的这个措辞由于缺陷报告 712被更改,因为此措辞未涵盖某些情况,但这些更改不会改变此情况的结果。
回答by SethML
This is really a flaw in C++11 - as others have explained, in C++11 a static constexpr member variable, unlike every other kind of constexpr global variable, has external linkage, thus must be explicitly defined somewhere.
这确实是 C++11 中的一个缺陷——正如其他人所解释的,在 C++11 中,静态 constexpr 成员变量与其他所有类型的 constexpr 全局变量不同,具有外部链接,因此必须在某处显式定义。
It's also worth noting that you can often in practice get away with static constexpr member variables without definitions when compiling with optimization, since they can end up inlined in all uses, but if you compile without optimization often your program will fail to link. This makes this a very common hidden trap - your program compiles fine with optimization, but as soon as you turn off optimization (perhaps for debugging), it fails to link.
还值得注意的是,在使用优化编译时,您通常可以在实践中摆脱没有定义的静态 constexpr 成员变量,因为它们最终可以在所有用途中内联,但是如果您在没有优化的情况下编译,您的程序通常将无法链接。这使它成为一个非常常见的隐藏陷阱 - 您的程序通过优化编译得很好,但是一旦您关闭优化(可能是为了调试),它就无法链接。
Good news though - this flaw is fixed in C++17! The approach is a bit convoluted though: in C++17, static constexpr member variables are implicitly inline. Having inline applied to variablesis a new concept in C++17, but it effectively means that they do not need an explicit definition anywhere.
不过好消息是——这个缺陷在 C++17 中得到了修复!不过,这种方法有点复杂:在 C++17 中,静态 constexpr 成员变量是隐式 inline。将内联应用于变量是 C++17 中的一个新概念,但这实际上意味着它们在任何地方都不需要显式定义。
回答by deddebme
Isn't the more elegant solution be changing the char[]
into:
不是更优雅的解决方案是char[]
改为:
static constexpr char * baz = "quz";
This way we can have the definition/declaration/initializer in 1 line of code.
这样我们就可以在 1 行代码中定义/声明/初始化。
回答by Josh Greifer
My workaround for the external linkage of static members is to use constexpr
reference member getters (which doesn't run into the problem @gnzlbg raised as a comment to the answer from @deddebme).
This idiom is important to me because I loathe having multiple .cpp files in my projects, and try to limit the number to one, which consists of nothing but #include
s and a main()
function.
我对静态成员的外部链接的解决方法是使用constexpr
引用成员 getter(这不会遇到@gnzlbg 作为对@deddebme 的答案的评论提出的问题)。
这个习惯用法对我很重要,因为我讨厌在我的项目中有多个 .cpp 文件,并试图将数量限制为一个,它只包含#include
s 和一个main()
函数。
// foo.hpp
struct foo {
static constexpr auto& baz() { return "quz"; }
};
// some.cpp
auto sz = sizeof(foo::baz()); // sz == 4
auto& foo_baz = foo::baz(); // note auto& not auto
auto sz2 = sizeof(foo_baz); // 4
auto name = typeid(foo_baz).name(); // something like 'char const[4]'
回答by Haishan Zhou
On my environment, gcc vesion is 5.4.0. Adding "-O2" can fix this compilation error. It seems gcc can handle this case when asking for optimization.
在我的环境中,gcc 版本是 5.4.0。添加“-O2”可以修复这个编译错误。在要求优化时,gcc 似乎可以处理这种情况。