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

问题描述 投票:157回答:6

我想在班上有一个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
}
c++ c++11 static-members constexpr
6个回答
151
投票

添加到您的cpp文件:

constexpr char foo::baz[];

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


58
投票

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]),

...

并添加[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而改变,因为有些案例没有涵盖这一措辞,但这些修改并未改变本案的结果。


31
投票

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

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

好消息 - 这个缺陷在C ++ 17中得到修复!这种方法有点令人费解:在C ++ 17中,静态constexpr成员变量are implicitly inline。拥有inline applied to variables是C ++ 17中的一个新概念,但它实际上意味着它们不需要任何地方的明确定义。


4
投票

是不是更优雅的解决方案是将char[]变成:

static constexpr char * baz = "quz";

这样我们就可以在一行代码中定义/声明/初始化。


2
投票

我对静态成员的外部链接的解决方法是使用constexpr引用成员getters(它不会遇到问题@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]'

0
投票

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

© www.soinside.com 2019 - 2024. All rights reserved.