未定义的对静态constexpr char []的引用
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了未定义的对静态constexpr char []的引用相关的知识,希望对你有一定的参考价值。
我想在班上有一个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
添加到您的cpp文件:
constexpr char foo::baz[];
原因:您必须提供静态成员的定义以及声明。声明和初始化器都在类定义中,但成员定义必须是分开的。
C++17 introduces inline variables
C ++ 17修复了constexpr静态成员变量的这个问题,如果它是ord-used则需要一个out-line定义。有关预C ++ 17的详细信息,请参阅下面的原始答案。
提案P0386 Inline Variables引入了将内联说明符应用于变量的功能。特别是对于这种情况,constexpr意味着内联静态成员变量。提案说:
内联说明符可以应用于变量以及函数。声明为内联的变量与内联声明的函数具有相同的语义:它可以在多个翻译单元中相同地定义,必须在每个翻译单元中定义,并且程序的行为就像是恰好是一个变量。
和修改[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)
- 结束例子]
Original Answer
在C ++ 03中,我们只允许为const积分或const枚举类型提供类初始化器,在C ++ 11中使用constexpr将其扩展为文字类型。
在C ++ 11中,我们不需要为静态constexpr成员提供命名空间作用域定义,如果它没有使用,我们可以从草案C ++ 11标准部分9.4.2
[class.static.data]中看到这一点。说(强调我的前进):
[...]可以使用constexpr说明符在类定义中声明文字类型的静态数据成员;如果是这样,它的声明应指定一个大括号或等于初始化器,其中作为赋值表达式的每个initializer子句都是一个常量表达式。 [注意:在这两种情况下,成员可能会出现在常量表达式中。 -end note]如果在程序中使用odr-used(3.2)并且命名空间作用域定义不包含初始化程序,则仍应在命名空间作用域中定义该成员。
那么问题就变成了,这里使用的是baz
odr:
std::string str(baz);
答案是肯定的,所以我们也需要命名空间范围定义。
那么我们如何确定变量是否使用了? 3.2
[basic.def.odr]部分中的原始C ++ 11措辞说:
除非是未评估的操作数(第5条)或其子表达式,否则可能会对表达式进行求值。名称显示为潜在评估表达式的变量是odr-used,除非它是满足出现在常量表达式(5.19)中的要求的对象,并且立即应用左值到右值转换(4.1)。
所以baz
确实产生一个常量表达式,但由于baz
是一个数组,所以不能立即应用左值到右值的转换,因为它不适用。 4.1
[conv.lval]部分对此进行了介绍,其中说:
非函数非数组类型T的glvalue(3.10)可以转换为prvalue。[...]
什么应用于数组到指针的转换。
[basic.def.odr]的这一措辞因Defect Report 712而改变,因为有些案例没有涵盖这一措辞,但这些修改并未改变本案的结果。
这实际上是C ++ 11中的一个缺陷 - 正如其他人所解释的那样,在C ++ 11中,静态constexpr成员变量与其他类型的constexpr全局变量不同,它具有外部链接,因此必须在某处明确定义。
还值得注意的是,在使用优化进行编译时,您通常可以在实践中使用静态constexpr成员变量而无需定义,因为它们最终可以在所有用途中内联,但如果您在没有优化的情况下进行编译,则程序将无法链接。这使得这是一个非常常见的隐藏陷阱 - 您的程序可以通过优化进行编译,但是一旦关闭优化(可能用于调试),它就无法链接。
好消息 - 这个缺陷在C ++ 17中得到修复!这种方法有点令人费解:在C ++ 17中,静态constexpr成员变量are implicitly inline。拥有inline applied to variables是C ++ 17中的一个新概念,但它实际上意味着它们不需要任何地方的明确定义。
是不是更优雅的解决方案是将char[]
变成:
static constexpr char * baz = "quz";
这样我们就可以在一行代码中定义/声明/初始化。
我对静态成员的外部链接的解决方法是使用constexpr
引用成员getters(它不会遇到问题@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]'
在我的环境中,gcc版本是5.4.0。添加“-O2”可以解决此编译错误。在要求优化时,gcc似乎可以处理这种情况。
以上是关于未定义的对静态constexpr char []的引用的主要内容,如果未能解决你的问题,请参考以下文章
c++11:为啥静态 constexpr 的类内初始化不是定义?
为啥这个 constexpr 静态成员函数在调用时不被视为 constexpr?