对静态constexpr char []的未定义引用


184

我想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
}

1
只是预感,如果baz例如int可以正常工作吗?然后可以访问它吗?这也可能是一个错误。
FailedDev 2011年

1
@Pubby:问题:它将在哪个翻译单元中定义?答:包括标题的所有内容。问题:违反一个定义规则。例外:可以在头文件中“初始化”编译时常量积分。
Mooing Duck 2011年

它像int@MooingDuck一样可以正常编译。作为非成员,它可以正常运行。那也不会违反规则吗?
Pubby 2011年

@ Pubby8:int作弊。作为非会员,这不应该被允许,除非为C ++ 11更改了规则(可能)
Mooing Duck 2011年

考虑到意见和支持,此问题需要更详细的答案,我在下面添加了该答案。
Shafik Yaghmour 2015年

Answers:


187

添加到您的cpp文件:

constexpr char foo::baz[];

原因:您必须提供静态成员的定义以及声明。声明和初始化程序放在类定义中,但成员定义必须分开。


70
这奇怪的眼神看着......它似乎因为没有提供一些信息,如果不是之前有编译器...
藤蔓

32
当您在.cpp文件中添加类声明时,它看起来更加不可思议!您可以在类声明中初始化该字段,但仍需要通过在该类下面编写constexpr char foo :: baz [] 来“ 声明 ”该字段。似乎使用constexpr的程序员可以按照一个奇怪的提示来编译程序:再次声明它。
Lukasz Czerwinski 2014年

5
@LukaszCzerwinski:您要查找的词是“定义”。
Kerrek SB 2014年

5
正确,没有新信息:使用decltype(foo::baz) constexpr foo::baz;
用户

6
如果将foo模板化,则表达式将是什么样?谢谢。
开平

80

C ++ 17引入了内联变量

C ++ 17解决了此问题,该问题适用于constexpr static需要使用脱机定义的成员变量(如果使用过时)。有关C ++ 17之前的详细信息,请参见此答案的后半部分。

提案P0386内联变量引入了将说明inline应用于变量的功能。特别是这种情况constexpr意味着inline静态成员变量。提案说:

内联说明符可以应用于变量以及函数。内联声明的变量与内联声明的函数具有相同的语义:可以在多个翻译单元中相同地对其进行定义,必须在每个使用该翻译单元的翻译单元中对其进行定义,并且程序的行为就像只有一个变量。

并修改了[basic.def] p2:

声明是一个定义,除非
...

  • 它在类定义之外声明了一个静态数据成员,并且该变量是使用constexpr说明符在类内定义的(不建议使用此用法;请参见[depr.static_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 ++ 14及更早版本

在C ++ 03中,只允许我们为const积分const枚举类型提供类内初始化程序,而在C ++ 11中,使用constexpr它扩展为文字类型

在C ++ 11中,constexpr如果未使用odr-used,则不需要为静态成员提供名称空间作用域定义,我们可以从C ++ 11标准草案9.4.2 [class.static.data]中看到这一点,即(重点是我的前进):

可以使用constexpr说明符在类定义中声明文字类型的静态数据成员;如果是这样,则其声明应指定大括号或相等的初始化程序,其中每个作为赋值表达式的初始化程序子句都是一个常量表达式。[注意:在这两种情况下,成员都可能出现在常量表达式中。— [end note] 如果在程序中使用了成员(3.2),则该成员仍应在命名空间范围中定义,并且命名空间范围定义不应包含初始化程序。

因此,问题就变成了这里baz 的用法

std::string str(baz); 

答案是肯定的,因此我们也需要命名空间范围定义。

那么我们如何确定变量是否被使用呢?3.2 [basic.def.odr]节中原始的C ++ 11措辞说:

除非表达式是未评估的操作数(第5条)或其子表达式,否则可能会对其进行评估。除非 其对象满足在常量表达式(5.19)中出现的要求并且立即应用左值到右值转换(4.1)否则使用名称显示为可能评估的表达式的变量。

baz确实会产生一个常数表达式,但是由于是数组,因此不适用于左值到右值转换baz4.1 [conv.lval]部分对此进行了介绍:

非函数,非数组类型T的glvalue(3.10)可以转换为prvalue.53 [...]

数组到指针转换中应用了什么。

由于缺陷报告712[basic.def.odr]的此措词已更改,因为此措词未涵盖某些情况,但这些更改不会更改此案例的结果。


所以我们清楚这constexpr与它完全无关吗?(baz无论如何都是常量表达式)
MM

如果成员不是a ,则需要@MattMcNabb好constexprintegral or enumeration type,否则,是的,重要的是它是一个常量表达式
Shafik Yaghmour 2015年

我相信,在第一段中,“ ord-used”应理解为“ odr-used”,但是我不确定C ++
Egor Pasko

37

这确实是C ++ 11中的缺陷-正如其他人所解释的那样,在C ++ 11中,静态constexpr成员变量与其他所有constexpr全局变量不同,它具有外部链接,因此必须在某个地方明确定义。

还要注意的是,在使用优化进行编译时,您实际上常常可以在没有定义的情况下使用静态constexpr成员变量,而无需定义,因为它们最终可能在所有用途中都内联,但是如果您在不进行优化的情况下进行编译,则您的程序通常将无法链接。这使它成为一个非常常见的隐藏陷阱-您的程序可以通过优化编译良好,但是一旦关闭优化(可能是用于调试),它就无法链接。

不过,好消息是-此缺陷已在C ++ 17中修复!但是,该方法有些复杂:在C ++ 17中,静态constexpr成员变量隐式内联。将内联应用于变量是C ++ 17中的一个新概念,但这实际上意味着它们在任何地方都不需要显式定义。


4
用于C ++ 17信息。您可以将此信息添加到接受的答案!
SR

5

将其更改char[]为不是更优雅的解决方案:

static constexpr char * baz = "quz";

这样,我们可以在1行代码中包含定义/声明/初始化器。


9
char[]您可以使用with sizeof来在编译时获取字符串的长度,而char *不能使用(它会返回指针类型的宽度,在这种情况下为1)。
gnzlbg

2
如果您想严格使用ISO C ++ 11,这也会生成警告。
Shital Shah

请参阅我的回答,该回答不存在sizeof问题,可以在“仅标头”解决方案中使用
Josh Greifer

4

对于静态成员的外部链接,我的解决方法是使用constexpr引用成员getter(不会遇到@gnzlbg作为对@deddebme答案的评论而引发的问题)。
这个习惯用法对我来说很重要,因为我讨厌项目中有多个.cpp文件,并尝试将数量限制为一个,除了#includes和一个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]'

-1

在我的环境中,gcc vesion是5.4.0。添加“ -O2”可以解决此编译错误。似乎gcc可以在请求优化时处理这种情况。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.