GCC可以编译可变参数模板,而Clang不能。

9

我正在阅读由Leor Zolman先生演示的C++11和C++14概述幻灯片。在第35页,他介绍了使用decltype进行求和操作的方法。

struct Sum {
  template <typename T>
  static T sum(T n) {
    return n;
  }
  template <typename T, typename... Args>
  /// static T sum(T n, Args... rest) {
  static auto sum(T n, Args... rest) -> decltype(n + sum(rest...)) {
    return n + sum(rest...);
  }
};

当使用这个片段Sum::sum(1, 2.3, 4, 5);时,clang-3.6(来自svn)无法使用-std=c++11/-std=c++1y编译,但是gcc-4.9可以成功编译。当然,在没有返回类型推断的情况下,两者都可以编译,但这涉及类型转换,无法得到预期的结果。
那么,这是否表明了clang的bug,还是因为gcc扩展(关于c++11或c++14)?

@dom0 我也这么猜,但不知道在草案中是如何解释的。 - Hongxu Chen
虽然这并没有回答你的问题,但是这个链接或许有关联:https://dev59.com/02865IYBdhLWcg3wlviq - Drax
@Drax 谢谢,现在我意识到了区别。 - Hongxu Chen
2
这是http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_closed.html#1433。 - Jonathan Wakely
1
而且 http://cplusplus.github.io/EWG/ewg-closed.html#104 - Jonathan Wakely
显示剩余3条评论
1个回答

10

Clang的行为是正确的。这是一个GCC的问题(演示中的声明也是不正确的)。 §3.3.2 [basic.scope.pdecl] / p1,6:

1.名称的声明点是在其完整的声明符(第8条)之后,初始化程序(如果有)之前立即进行的, 除非下面有说明。

6. 类成员的声明点之后,该成员名称可以在其类的作用域中查找。

而§3.3.7 [basic.scope.class] / p1则如下所述:

以下规则描述了在类中声明名称的范围。

1) 在类中声明的名称的潜在作用域不仅包括名称后的声明区域, 还包括该类中所有函数体、默认参数、异常规范和非静态数据成员的大括号或等于初始化器(包括嵌套类中的这些东西)。

尾随返回类型不在该列表中。

尾随返回类型是声明符的一部分(§8 [dcl.decl] / p4):

declarator:
    ptr-declarator
    noptr-declarator parameters-and-qualifiers trailing-return-type

因此,可变参数版本的sum在其自身的尾返回类型中不在作用域内,无法通过名称查找找到。

在C++14中,只需使用实际返回类型推导(并省略尾返回类型)。 在C++11中,您可以使用类模板和一个简单的函数模板来转发:

template<class T, class... Args>
struct Sum {
    static auto sum(T n, Args... rest) -> decltype(n + Sum<Args...>::sum(rest...)) {
        return n + Sum<Args...>::sum(rest...);
    }
};

template<class T>
struct Sum<T>{
    static T sum(T n) { return n; }
};

template<class T, class... Args>
auto sum(T n, Args... rest) -> decltype(Sum<T, Args...>::sum(n, rest...)){
    return Sum<T, Args...>::sum(n, rest...);
}

所以,所呈现的代码不是标准的,而且没有C++14返回类型推导,似乎没有优雅的解决方案,对吗? - Hongxu Chen
从我的角度来看,将模板类的非模板成员函数转发应该可以工作(template <typename T, typename... Args> auto sum(T n, Args... rest) -> decltype(SumImpl<T, Args...>::impl(n, rest...)) { return SumImpl<T, Args...>::impl(n, rest...); },其中SumImpl<T, Args...>::impl使用SumImpl<Args...>::impl是可以接受的。 - user743382
@HongxuChen 请看我的编辑。是否算得上优雅,我不知道。 - T.C.

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