gcc can compile a variadic template while clang cannot
Asked Answered
O

1

9

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

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.

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

Oscilloscope answered 9/10, 2014 at 8:52 Comment(8)
Might be a Clang bug, since Clang claims to support all of C++11 and all C++14 draft features.Tasteless
@Tasteless i also guess so, but don't know where and how this is explained in the draft.Oscilloscope
Although it doesn't answer your question this is related: https://mcmap.net/q/402539/-trailing-return-type-using-decltype-with-a-variadic-template-function/1147772Carousal
@Carousal Strange enough that rextester.com/BVNR10123 can get the correct result with c++14, but with decltype it can only be put inside the class(add clang doesn't compile).Oscilloscope
@HongxuChen Nope i'm pretty sure what's happening is that your sum template function is not available for calling until its return type is complete, see T.C.'s answerCarousal
@Carousal thanks, now i realized the difference.Oscilloscope
This is open-std.org/jtc1/sc22/wg21/docs/cwg_closed.html#1433Ensoul
And cplusplus.github.io/EWG/ewg-closed.html#104Ensoul
A
10

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 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 After the point of declaration of a class member, the member name can be looked up in the scope of its class.

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

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

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 are not in that list.

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

declarator:
    ptr-declarator
    noptr-declarator parameters-and-qualifiers 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.

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...);
}
Arbour answered 9/10, 2014 at 9:18 Comment(3)
So the code presented is not of standard, and without c++14 return type deduction there seems no elegant solution for this, right?Oscilloscope
Off of the top of my head, forwarding to a non-template member function of a template class should work (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...); }, where it is acceptable for SumImpl<T, Args...>::impl to use SumImpl<Args...>::impl.Alburnum
@HongxuChen See my edit. Whether that counts as elegant, I don't know.Arbour

© 2022 - 2024 — McMap. All rights reserved.