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

时间:2011-11-04 23:11:45

标签: c++ c++11 static-members constexpr

我想在班上有一个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
}

6 个答案:

答案 0 :(得分:150)

添加到您的cpp文件:

constexpr char foo::baz[];

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

答案 1 :(得分:57)

C ++ 17引入了内联变量

C ++ 17修复了constexpr静态成员变量的这个问题,如果它是ord-used则需要一个out-line定义。有关C + 17之前的详细信息,请参阅下面的原始答案。

提案P0386 Inline Variables引入了将内联说明符应用于变量的功能。特别是对于这种情况,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 ++ 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似乎可以处理这种情况。