【问题标题】:erroneous explicit template specialization of variable template in gccgcc中变量模板的错误显式模板特化
【发布时间】:2017-10-15 07:26:53
【问题描述】:

// i.h

template<int> extern int const i;

// i.cpp

#include "i.h"
template<> extern int constexpr i<0> = 42;

// main.cpp

#include "i.h"
int main()
{
  return i<0>;
}

在 C++14/17 模式下,这会在 clang 中返回 42,但在 gcc 中会出现错误:“显式模板专业化不能有存储类”。

这是 gcc 中的错误吗?

【问题讨论】:

  • What's the right way to specialize a template when using "extern template"? 的可能重复项 特化是界面的一部分 - 您不能像这样将其隐藏在 .cpp 文件中。例如,特化可能是对某些类型 T 进行 sfinae away 或 static_assert,这需要在客户端代码中知道。
  • 不,抱歉,这是一个完全不同的问题。
  • 嗯。你能展示最初的模板声明吗?你的意思是在.cpp文件中写contexpr,在.h文件中写const吗?
  • 嗯。你想要一个明确的实例化或设置链接吗?请注意,默认情况下全局模板名称具有外部链接...换句话说,您能解释一下最终意图是什么吗?
  • @JohanLundberg 上面的代码是完整的。是的,我的意思是我写的一切。没有错别字。

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


【解决方案1】:

对于整个问题有一个相当简单的解决方案。另请参阅 ISO C++ 标准 - 讨论论坛上的 this 帖子和 Richard Smith 的回复。

1。 extern 不得在显式特化中指定

所以回答最初的问题:不,这不是 gcc 中的错误,报告错误是正确的(正如 Massimiliano Janes 已经回答的那样)。

相比之下,clang 实际上有一个错误(正如 Massimiliano Janes 已经猜到的),因为extern 被接受了。可能 clang 默默接受它,因为它和主模板一样。

2。 理论上(根据标准)解决方案是删除extern,因为模板链接是按名称进行的,因此专业化“继承”主模板的链接(再次参见 Massimiliano Janes 的回答)

但实际上它不起作用,因为这里的两个编译器都不正确,并且显式特化错误地具有内部链接而不是外部主模板的链接。

3。 总结:

gcc 永远不会编译 (1) 中正确但 (2) 中不正确的编译器。 clang 在 (1) 中编译不正确,但在 (2) 中编译不正确。

我将为 clang 提交一份错误报告。如果有人感兴趣,请随时为 gcc 提交错误。我不会这样做,因为(很遗憾)我不能在我的开发环境 Visual Studio 中使用 gcc。

【讨论】:

    【解决方案2】:

    主变量模板必须声明为 extern,因为它是 const 并且我不想在头文件中使用初始化器(就像普通的“extern int const i;”一样)。相反,我希望在某些源文件中进行专业化定义。

    解决方案应该是在专业化中删除“extern”。

    因为

    [declarations/specifiers-7.1.1]不应在显式特化中指定存储类说明符

    理由是所有专业都应该具有相同的链接(例如参见defect report 605)。所以,这里的 clang 似乎是错误的。

    无论如何,鉴于编译器在这方面表现得非常疯狂,解决方法可能类似于

    // i.h
    template<int I> struct i_impl{ static const int value; };
    template<int I> int const i = i_impl<I>::value;
    
    // i.cpp
    #include <i.h>
    template<> const int i_impl<0>::value = 42;
    

    【讨论】:

    • "解决方案应该是在特化中删除 'extern'" 但是如果它被删除,那么 i 就像普通的 const 声明的变量一样具有内部链接。因此,在 main() 中存在对 i 的未定义引用。那不是我想要的。此外,专业化 i 的链接将与违反您提到的基本原理的主变量模板不同。所以放弃 extern 不是一个解决方案。
    • 从概念上讲,变量模板 i 只是一个“模板化的” extern int const 并且它在某些源中的定义必须具有 extern 说明符,如果它之前没有声明(在这种情况下,可以删除 extern 说明符) .换句话说,带有 extern 说明符的特化 i 对我来说似乎是完全合理的。但是 7.1.1(或 C++17 中的 10.1.1)相矛盾,所以不幸的是,clang 似乎是错误的(即使使用 -pedantic-errors 编译)而 gcc 是正确的。还是标准在这里有缺陷?
    • 最后最新的MSVC在main.cpp中报错“'i': an object of const-qualified type must be initialized”。这对我来说似乎完全错误,因为我被宣布为外部。所以三个不同的编译器会给出三种不同的结果。这不是很好。
    • @Rakete1111 它是,对于显式实例化,extern 应该在模板之前...
    • @MassimilianoJanes 哦,天哪,那是真的。不敢相信我错过了,谢谢:P
    【解决方案3】:

    根据N4340

    不应使用除 thread_local 之外的存储类说明符 在显式特化 (14.7.3) 或显式 实例化 (14.7.2) 指令。

    所以,extern 说明符会发生这种情况。只需删除专业化上的任何存储说明符。在您的代码中删除 extern 说明符。如:

    template<> 
    int constexpr i<0> = 42;
    

    【讨论】:

    • 这不是解决办法。没有外部说明符 i 具有内部链接(请参阅我对第一个答案的第一条评论)。
    • @xy this is 一个标准的解决方案(尽管它几乎与另一个答案相同),遗憾的是由于编译器惯性,它在实践中并不适用......
    • 什么意思?你的意思是如果 extern 没有在特化上指定,那么 i 具有标准的外部链接?你知道标准中的证明在哪里吗?我问是因为如果这真的是真的,那么 clang 和 gcc 都是错误的,因为在这种情况下两者都报告“在 main() 中未定义对 i 的引用”。
    • @MassimilianoJanes 仅供参考:我可以通过委员会成员的更多见解来回答我自己的问题(见下文)
    • @rsp 仅供参考:我可以通过委员会成员的更多见解来回答我自己的问题(见上文)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多