gcc 可以编译可变参数模板,而 clang 不能

2021-12-13 00:00:00 gcc templates clang c++ c++11

我正在阅读一些名为 C++11 和 C++14 概述,由 Leor Zolman 先生提出.在第 35 页,他介绍了一种使用 decltype 进行求和运算的方法.

I'm reading some slides named An Overview of C++11 and C++14 presented by Mr. Leor Zolman. At Page 35 he introduces a way to do the sum operation with 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(from svn) 无法用 -std=c++ 编译它11/-std=c++1y 但 gcc-4.9 成功了.当然,如果没有对返回类型进行类型推导,两者都可以编译,但这涉及类型转换,无法得到预期的结果.

When using this snippets forSum::sum(1, 2.3, 4, 5); clang-3.6(from svn) fails to compile this with -std=c++11/-std=c++1y but gcc-4.9 succeeds. Of course without type deduction for the return type both compile, but that involves type conversion and cannot get the expected result.

那么这是否表示一个 clang 错误,或者是因为 gcc 扩展(就 c++11 或 c++14 而言)?

So does this indicate a clang bug, or is because of a gcc extension(in respect of c++11 or c++14)?

推荐答案

Clang 的行为是正确的.这是一个 GCC 错误(并且演示文稿中的声明也不正确).§3.3.2 [basic.scope.pdecl]/p1,6:

Clang's behavior is correct. This is a GCC bug (and the claim in the presentation is also incorrect). §3.3.2 [basic.scope.pdecl]/p1,6:

1 名称的声明点紧随其后完整的声明符(第 8 条)及其初始化程序(如果有)之前,除非如下所述.

1 The point of declaration for a name is immediately after its complete declarator (Clause 8) and before its initializer (if any), except as noted below.

6 类成员声明点后,成员名可以在其类的范围内查找.

6 After the point of declaration of a class member, the member name can be looked up in the scope of its class.

第 3.3.7 节 [basic.scope.class]/p1 说

And §3.3.7 [basic.scope.class]/p1 says

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

The following rules describe the scope of names declared in classes.

1) 在类中声明的名称的潜在范围不仅包括名称声明点之后的声明区域,还有所有的函数体,默认参数,exception-specifications 和 brace-or-equal-initializers该类中的非静态数据成员(包括嵌套类).

1) The potential scope of a name declared in a class consists not only of the declarative region following the name’s point of declaration, but also of all function bodies, default arguments, exception-specifications, and brace-or-equal-initializers of non-static data members in that class (including such things in nested classes).

trailing-return-types 不在该列表中.

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

The trailing return type is part of the declarator (§8 [dcl.decl]/p4):

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

因此 sum 的可变参数版本不在其自己的trailing-return-type 范围内,并且无法通过名称查找找到.

and so the variadic version of sum isn't in scope within its own trailing-return-type and cannot be found by name lookup.

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

In C++14, simply use actual return type deduction (and omit the trailing return type). In C++11, you may use a class template instead and a function template that simply forwards:

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...);
}

相关文章