我想在班上有一个static const
char
数组。海湾合作委员会抱怨并告诉我应该使用constexpr
,虽然现在它告诉我这是一个未定义的参考。如果我使数组成为非成员,那么它将编译。发生了什么事?
// .hpp
struct foo {
void bar();
static constexpr char baz[] = "quz";
};
// .cpp
void foo::bar() {
std::string str(baz); // undefined reference to baz
}
答案 0 :(得分:150)
添加到您的cpp文件:
constexpr char foo::baz[];
原因:您必须提供静态成员的定义以及声明。声明和初始化器都在类定义中,但成员定义必须是分开的。
答案 1 :(得分:57)
C ++ 17修复了constexpr静态成员变量的这个问题,如果它是ord-used则需要一个out-line定义。有关C + 17之前的详细信息,请参阅下面的原始答案。
提案P0386 Inline Variables引入了将内联说明符应用于变量的功能。特别是对于这种情况,constexpr意味着内联静态成员变量。提案说:
内联说明符可以应用于变量和函数。声明的变量 inline与内联声明的函数具有相同的语义:它可以相同地定义 必须在每个翻译单元中定义多个翻译单元,并且 程序的行为就好像只有一个变量。
并修改了[basic.def] p2:
声明是一个定义,除非是 ...
- 它在类定义之外声明了一个静态数据成员,并且该变量是在类中使用constexpr说明符定义的(不推荐使用此用法;请参阅[depr.static_constexpr]),
...
为了与之前的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 ++ 03中,我们只允许使用 constexpr <在C ++ 11中为 const积分或 const枚举类型提供类初始化器/ em>这已扩展为文字类型。
在C ++ 11中,我们不需要为静态constexpr成员提供命名空间作用域定义,如果它不是 odr-used ,我们可以从草案C ++ 11标准中看到这一点部分9.4.2
[class.static.data] 表示(强调我的前进):
[...]可以在类中声明文字类型的静态数据成员 使用constexpr说明符定义;如果是的话,其声明应 指定一个brace-or-equal-initializer,其中包含每个initializer子句 这是一个赋值表达式是一个常量表达式。 [注意:在 在这两种情况下,成员都可能出现在常量表达式中。 -结束 注意 ] 如果程序中的odr-used(3.2)和名称空间作用域定义,该成员仍应在名称空间作用域中定义 不得包含初始化程序。
那么问题就变成了baz
odr-used :
std::string str(baz);
,答案是是,因此我们也需要命名空间范围定义。
那么我们如何确定变量是否为 odr-used ? 3.2
[basic.def.odr] 部分中的原始C ++ 11措辞说:
表达式可能会被评估,除非它是未评估的 操作数(第5条)或其子表达式。一个名称的变量 表现为潜在评估的表达除非,否则使用 它是满足出现在a中的要求的对象 常量表达式(5.19)和左值到右值的转换 (4.1)立即应用。
所以baz
会产生常量表达式,但左值到右值转换不会立即应用,因为它不适用{{1作为一个数组。这一点将在baz
[conv.lval] 部分中介绍:
非功能性非数组类型T 的glvalue(3.10)可以是 转换为prvalue.53 [...]
在数组到指针转换中应用了什么。
由于Defect Report 712, [basic.def.odr] 的这一措辞发生了变化,因为这些措辞并未涉及某些案例,但这些更改并未改变此案例的结果。
答案 2 :(得分:31)
这实际上是C ++ 11中的一个缺陷 - 正如其他人所解释的那样,在C ++ 11中,静态constexpr成员变量与其他类型的constexpr全局变量不同,具有外部链接,因此必须在某处明确定义。
值得注意的是,在使用优化进行编译时,您通常可以在实践中使用静态constexpr成员变量而无需定义,因为它们最终可以在所有用途中内联,但如果您在没有优化的情况下进行编译,则程序将无法链接。这使得这是一个非常常见的隐藏陷阱 - 您的程序通过优化编译得很好,但是一旦关闭优化(可能用于调试),它就无法链接。
好消息 - 这个缺陷在C ++ 17中得到修复!这种方法有点令人费解:在C ++ 17中,静态constexpr成员变量are implicitly inline。拥有inline applied to variables是C ++ 17中的一个新概念,但它实际上意味着它们不需要在任何地方进行显式定义。
答案 3 :(得分:4)
更优雅的解决方案是将char[]
更改为:
static constexpr char * baz = "quz";
这样我们可以在一行代码中定义/声明/初始化。
答案 4 :(得分:2)
对于静态成员的外部链接,我的解决方法是使用constexpr
引用成员getter(不会遇到@gnzlbg作为对@deddebme答案的评论而引起的问题)。
这个习惯用法对我来说很重要,因为我讨厌在项目中拥有多个.cpp文件,并尝试将其限制为一个,其中只包含#include
和一个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]'
答案 5 :(得分:0)
在我的环境中,gcc vesion是5.4.0。添加“ -O2”可以解决此编译错误。要求优化时,gcc似乎可以处理这种情况。