【问题标题】:constexpr array member with template specialization: inconsistent behavior cross compilers具有模板特化的 constexpr 数组成员:不一致的行为交叉编译器
【发布时间】:2018-01-20 22:45:02
【问题描述】:

考虑以下代码:

#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&lt;int&gt;::valuefoo&lt;int&gt;::value。当我更改为clang++ -std=c++17 时,只有一个未定义的符号:foo&lt;int&gt;::value。我的 clang++ 版本是 5.0。

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

我正好有两件事要问。

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

我已经搜索并阅读了许多 cppreference 页面,但没有发现任何与此现象真正相关的内容。特别是对于带有-std=c++17 的clang++,行为真的很奇怪,因为bar&lt;int&gt; 通过但foo&lt;int&gt; 失败,唯一的区别是foo&lt;int&gt; 是一个专业化。我从http://en.cppreference.com/w/cpp/language/constexpr 读到

在函数或静态成员变量(C++17 起)声明中使用的 constexpr 说明符意味着内联。

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

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

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

【问题讨论】:

  • 使用 Visual Studio 2017 编译良好 - 奇怪的是,即使没有明确指定 C++14/C++17。
  • @CookiePLMonster g++ 也成功为 -std=c++11 编译。但是对于 clang++,可以通过上面链接中提到的技巧来避免 c++11 错误。但是,c++17版本我什至找不到办法让它编译。

标签: c++ c++14 constexpr clang++ c++17


【解决方案1】:

直到 C++17,原因与the question you found 中所述的完全一样(我认为 Shafik Yaghmour 发布的答案更准确地解释了这个问题)。简而言之,如果成员为odr-used,则仍然需要定义constexpr静态数据成员。

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


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

... 使用 constexpr 说明符声明的函数或静态数据成员隐含地是内联函数或变量 ([dcl.inline])。

[basic.def] paragraph 2 中说

声明是一个定义除非

  • ...

  • 它在类定义([class.mem]、[class.static])中声明了一个非内联静态数据成员,

  • ...

因此,不需要在命名空间范围内进行此类定义。

另外,[depr.static_constexpr] paragraph 1中的现行标准说

为了与以前的 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++17 开始你最好避免这样的定义。

当我更改为clang++ -std=c++17 时,只有一个未定义的符号:foo&lt;int&gt;::value

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

【讨论】:

  • 所以总结一下:1) 因为bar&lt;int&gt;::value[2] 是ODR 使用,g++ 在-std=c++14 中接受这个模板代码在技术上是一个错误。在上面的链接问题中,没有使用模板的代码被正确拒绝。 2) clang++ 5.0 无法识别-std=c++17 中的模板特化案例是clang++ 的错误。感谢您提供这些最新的详细信息。
  • @liliscent 此外,bar&lt;int&gt;::value[2]CWG 1926 之后不再是 odr-use。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-08-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-07-02
  • 1970-01-01
相关资源
最近更新 更多