静态constexpr成员的未定义引用错误

26

考虑以下代码:

#include <vector>

struct A {
  static constexpr int kDefaultValue = -1;
  std::vector<int> v;
  A(int n): v(n, A::kDefaultValue) {}
};

int main() {
  A(10);
  return 0;
}

无法链接(在OS X上,llvm clang、gcc 4.9都无法链接):

Undefined symbols for architecture x86_64:
  "A::kDefaultValue", referenced from:
      A::(int) in main.cpp.o
ld: symbol(s) not found for architecture x86_64
问题是什么?它可以通过将A::kDefaultValue转换为int来修复。或者通过将kDefaultValue移出A。这两种情况似乎都很丑陋。这是另一种制作链接的方式吗?

1
请使用支持C++17的编译器。 - songyuanyao
宋元耀:尝试使用gcc-6编译C++14和C++17,但结果相同。 - y0prst
@y0prst,使用gcc7是可以的。http://melpon.org/wandbox/permlink/izFx4rwqLyX8QR7x - songyuanyao
@songyuanyao:不错!我想知道在C++17中改变了什么,以使这段代码能够工作。不要认为这只是编译器的错误 :) - y0prst
1
另一个解决方法是将 kDefaultValue 更改为 static constexpr std::integral_constant<int, -1> kDefaultValue{};。不需要外部定义,v(n, A::kDefaultValue) 将正常工作,并且适用于 C++11 及以后的版本。在线演示 - ildjarn
显示剩余3条评论
2个回答

31

这种行为一次又一次地困扰着我。问题的根源在于你的

A(int n): v(n, A::kDefaultValue) {}

odr-uses static constexpr 成员,因为 v 的构造函数接受一个常量引用作为第二个参数。Odr-usage 要求在某处有定义,即:

const int A::kDefaultValue;
在一些编译单元中(被编译并链接到main()),需要这样做。 然而,C++17已经放弃了这个要求,并且相应的定义(如上所述)已经被弃用。
然而,并不总是可能进行定义(例如对于类模板的成员),避免定义和错误的最简单方法是:
A(int n): v(n, int(A::kDefaultValue)) {}

创建了一个临时变量并将其传递给 v 的构造函数(但由于后者是完全内联的,编译器可能会进行优化)。


值得使用constexpr函数来代替静态成员吗?static constexpr int default_value() { return -1; }; 看起来它能够在所有标准中工作,并且没有任何缺点。 - y0prst
是的,这是另一种可能性;对于所有这些事情,都是品味和风格的问题。标准库在其 numeric_limits<> 特性类中使用 static 成员。 - Walter
顺便提一句,有趣的是,例如使用“numeric_limits<int>::radix”,它是“struct numeric_limits”的静态constexpr成员,却不会失败。 - y0prst
@y0prst 这是因为标准库的实现在某个地方提供了这些静态变量的定义(然后链接器找到它们)。 - Walter

13

自C++17起,行为已更改。在C++17之前,即使是constexrp静态数据成员也必须在类定义内初始化,并且仍然需要在命名空间作用域进行定义;自C++17以来,不再需要在命名空间作用域中重新声明。

如果声明了constexpr静态数据成员,则它隐式地是inline的,并且不需要在命名空间作用域下重新声明。仍然允许此形式上的重新声明(曾经像上面展示的那样是必需的),但现在已弃用。(自C++17开始)

使用支持C++17的编译器编译您的代码将正常工作。

使用gcc7的在线演示


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接