具有模板专业化的 constexpr 数组成员:不一致的行为交叉编译器

constexpr array member with template specialization: inconsistent behavior cross compilers

考虑以下代码:

#include <iostream>

template<class T>
struct foo {};

template<>
struct foo<int> {
  static constexpr char value[] = "abcde";
};

template<class T>
struct bar {
  static constexpr char value[] = "abcde";
};

template<class T>
struct baz {
  static constexpr int value = 12345;
};

int main() {
    char c = foo<int>::value[2];
    char d = bar<int>::value[2];
    int e = baz<int>::value;

    std::cout << c << d << e << "\n";
    
}

当使用 clang++ -std=c++14 ./test_foo.cc 进行编译时,出现未定义符号的链接器错误:bar<int>::valuefoo<int>::value。当我更改为 clang++ -std=c++17 时,只有一个未定义的符号:foo<int>::value。我的 clang++ 版本是 5.0.

然而,当我尝试g++ -std=c++14 ./test_foo.cc时,编译成功了。我的 g++ 版本是 5.4.0.

我正好有两件事要问。

1) 从 C++ 标准的角度来看,哪个编译器的行为正确?

我用谷歌搜索并阅读了许多 cppreference 页面,但没有发现任何与此现象真正相关的内容。尤其是带有-std=c++17的clang++,行为真的很奇怪,因为bar<int>通过了而foo<int>失败了,唯一的区别是foo<int>是一个特化。我从 http://en.cppreference.com/w/cpp/language/constexpr 那里读到

A constexpr specifier used in a function or static member variable (since C++17) declaration implies inline.

因此,模板专业化 foo<int> 似乎没有理由失败。此外,我在链接之前查看了生成的目标文件,对 foo<int>::value[2]; 的访问是 而不是 在编译时完成的,正如人们所期望的那样。我高度怀疑 clang++ 编译器有问题。

2) 如何处理这个 clang++ 链接错误?

我尝试了类似 Undefined reference to static constexpr char[] 的方法,但最终我找不到任何方法来克服这个链接错误。所以我只是想知道是否有办法让这个链接成功。

直到C++17,原因与the question you found (I think the answer posted by Shafik Yaghmour explains this problem more exactly). In short, the definition of a constexpr static data member is still required if the member is odr-used.

中所述完全相同

您可以 resolve the problem 通过提供这些变量的定义 直到 C++17(即使用 -std=c++14)。


自 C++17 起,[dcl.constexpr] paragraph 1 中的当前标准表示

... A function or static data member declared with the constexpr specifier is implicitly an inline function or variable ([dcl.inline]).

[basic.def] paragraph 2中说

A declaration is a definition unless

  • ...

  • it declares a non-inline static data member in a class definition ([class.mem], [class.static]),

  • ...

因此不需要命名空间范围内的此类定义。

此外,[depr.static_constexpr] paragraph 1中的当前标准说

For compatibility with prior C++ International Standards, a constexpr static data member may be redundantly redeclared outside the class with no initializer. This usage is deprecated. [ Example:

struct A {
  static constexpr int n = 5;  // definition (declaration in C++ 2014)
};

constexpr int A::n;  // redundant declaration (definition in C++ 2014)

— end example ]

所以从 C++17 开始你最好避免这样的定义。

When I change to clang++ -std=c++17, then only one undefined symbol: foo<int>::value.

这是一个 Clang 错误。无论如何,它 works well 用于 Clang HEAD 7.0.0。