【问题标题】:out-of-line constructor template works in GCC fails in Clang在 GCC 中工作的离线构造函数模板在 Clang 中失败
【发布时间】:2018-04-16 16:58:49
【问题描述】:

以下代码在 GCC 中运行良好,但在 clang 中编译失败:

#include <iostream>
#include <string>

template <typename T>
struct A {
template <typename C>
  A(const C& c) { std::cout << "base" << std::endl; }
};

template <>
template <>
A<std::string>::A<std::string>(const std::string& s) { 
  std::cout << s << std::endl; 
}

int main()
{
  std::string f("foo");
  A<std::string> a(f);
  A<std::string> b(1.2);
}

GCC 输出:

foo
base

但是 Clang 给出以下错误:

source_file.cpp:14:17: error: out-of-line constructor for 'A' cannot have 
template arguments
A<std::string>::A<std::string>(const std::string& s) {              
                ^~~~~~~~~~~~~~
1 error generated.

我只是想确认是否有人知道 clang 是否 正确 不允许这样做,或者它是否是 clang 中的一个错误。如果我删除了显式模板参数,那么 clang 可以很好地推断模板参数,编译并产生正确的输出。只有显式模板参数才会编译失败,我想不出标准中有什么会不允许这样做。

谢谢

【问题讨论】:

标签: c++ templates language-lawyer template-specialization


【解决方案1】:

正如您已经注意到的,您不需要在构造函数模板特化中重复模板参数,因为它是推导出来的。 A&lt;std::string&gt;::A 应该足够了。

template <>
template <>
A<std::string>::A(const std::string& s) { 
  std::cout << s << std::endl; 
}

主要问题是构造函数实际上没有名字。仍然有一个开放的 C++ 标准问题581. Can a templated constructor be explicitly instantiated or specialized?,并附有 2006 年的以下注释:

据观察,实际上从来没有必要在构造函数声明中显式指定模板参数,因为根据定义,参数都是可推导的,因此可以省略。

Clang cmets 提到 [class.qual]p2[8][9] 并发出 1435[4] 作为抱怨和拒绝模板的论据论据。

6.4.3.1 Class members [class.qual]

[...]

2 在不忽略函数名称且 nested-name-specifier 指定类 C 的查找中:

— (2.1) 如果在 nested-name-specfier 之后指定的名称,当在 C 中查找时,是 C 的注入类名(第 12 条),或者

[...]

该名称被认为是命名类 C 的构造函数。

唯一提到转换成员函数模板和构造函数成员函数模板不能与显式模板参数一起使用的地方是非规范性注释。

17.6.2 Member templates [temp.mem]

[...]

5 转换函数模板的特化以与转换为相同类型的非模板转换函数相同的方式引用。 [...]

[注意:因为显式模板实参列表跟在函数模板名之后,并且因为转换成员函数模板和构造函数成员函数模板在调用时不使用函数名,所以无法为这些提供显式模板实参列表功能模板。 ——尾注]

【讨论】:

  • 它们是两个不同的模板参数,我知道由于参数推导它不是需要,但理论上为什么GCC允许它存在而clang不允许它?如果我只是尝试实例化模板而不是定义专业化,这也会出现:template A&lt;std::string&gt;::A&lt;std::string&gt;(const std::string&amp; s);
  • 感谢更新以回答这里的其他困惑!我一直在寻找标准以试图解决这个问题,而缺少的部分是嵌套名称说明符查找。
  • 有趣的是,因为嵌套名称规范后面的名称是注入的类名称,所以可以写A&lt;std::string&gt;::A::A::A,它仍然会编译
【解决方案2】:

Clang 是正确的。

引用自[temp.mem]/5

[ 注意:因为显式模板实参列表跟在函数模板名之后,并且因为转换成员函数模板和构造函数成员函数模板是在不使用函数名的情况下调用的,所以无法为这些提供显式模板实参列表功能模板。 — 尾注 ]

【讨论】:

  • 所以一位同事实际上指出,实际上我们的问题不是模板专业化,而是模板实例化/声明。如果我们忘记了特化并将其切换为仅使用一行来实例化模板(在 gcc 中我们使用 -fno-implicit-templates),我们将得到这一行:template A&lt;std::string&gt;::A&lt;std::string&gt;(const std::string&amp; s);,它会产生相同的错误。这属于同一部分吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-12-12
  • 1970-01-01
  • 2014-07-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多