4

At what point is a variadic template considered "declared"? This compiles under clang++ 3.4, but not under g++ 4.8.2.

template <typename T>
const T &sum(const T &v) { return v; }

template <typename T, typename ... Ts>
auto sum(const T &v, const Ts & ... params) -> decltype(v + sum(params...));

template <typename T, typename ... Ts>
auto sum(const T &v, const Ts & ... params) -> decltype(v + sum(params...)) {
    return v + sum(params...);
}

int main() {
    sum(1, 2, 3);
}

Apparently g++ won't match the function itself in the trailing return type. The error from g++ 4.8.2 is:

sum.cpp: In function 'int main()':
sum.cpp:13:16: error: no matching function for call to 'sum(int, int, int)'
     sum(1, 2, 3);
                ^
sum.cpp:13:16: note: candidates are:
sum.cpp:2:10: note: template<class T> const T& sum(const T&)
 const T &sum(const T &v) { return v; }
          ^
sum.cpp:2:10: note:   template argument deduction/substitution failed:
sum.cpp:13:16: note:   candidate expects 1 argument, 3 provided
     sum(1, 2, 3);
                ^
sum.cpp:8:6: note: template<class T, class ... Ts> decltype ((v + sum(sum::params ...))) sum(const T&, const Ts& ...)
 auto sum(const T &v, const Ts & ... params) -> decltype(v + sum(params...)) {
      ^
sum.cpp:8:6: note:   template argument deduction/substitution failed:
sum.cpp: In substitution of 'template<class T, class ... Ts> decltype ((v + sum(sum::params ...))) sum(const T&, const Ts& ...) [with T = int; Ts = {int, int}]':
sum.cpp:13:16:   required from here
sum.cpp:5:74: error: no matching function for call to 'sum(const int&, const int&)'
 auto sum(const T &v, const Ts & ... params) -> decltype(v + sum(params...));
                                                                          ^
sum.cpp:5:74: note: candidate is:
sum.cpp:2:10: note: template<class T> const T& sum(const T&)
 const T &sum(const T &v) { return v; }
          ^
sum.cpp:2:10: note:   template argument deduction/substitution failed:
sum.cpp:5:74: note:   candidate expects 1 argument, 2 provided
 auto sum(const T &v, const Ts & ... params) -> decltype(v + sum(params...));
                                                                      ^

Addendum: If I delete the declaration of the variadic template, both clang++ and g++ give errors.

Addedum 2: I see that a similar question has been asked before. I guess the real question here is why it works with one compiler and not the other. Also, I can make it work with g++ by forcing ADL at the POI by using non-primitive arguments to sum().

Addendum 3: This works under both clang++ and g++:

class A {
};
A operator+(const A &, const A &) {
    return A();
}

template <typename T>
const T &sum(const T &v) { return v; }

/*
template <typename T, typename ... Ts>
auto sum(const T &v, const Ts & ... params) -> decltype(v + sum(params...));
*/

template <typename T, typename ... Ts>
auto sum(const T &v, const Ts & ... params) -> decltype(v + sum(params...)) {
    return v + sum(params...);
}

int main() {
    //sum(1, 2, 3);
    sum(A(), A(), A());
}
Constructor
  • 7,273
  • 2
  • 24
  • 66
kec
  • 2,099
  • 11
  • 17
  • 1
    possible duplicate of [trailing return type using decltype with a variadic template function](http://stackoverflow.com/questions/3744400/trailing-return-type-using-decltype-with-a-variadic-template-function) – Praetorian Apr 11 '14 at 02:51
  • 2
    This is addressed by [issue 1433](http://wg21.cmeerw.net/cwg/issue1433), which is still active. The relevant sections from the standard are §8.3.5/2 and §3.3.2/1 – Praetorian Apr 11 '14 at 02:53
  • @Praetorian: Yeah, I originally thought that was simply because the point of declaration was after the trailing return type. But then I got it working with clang++ by first declaring the template. I lean towards thinking that this is a clang++ bug. – kec Apr 11 '14 at 02:56
  • Since clang accepts your code and produces the correct result, let's be more generous and call it an extension :) And, to be fair, the resolution probably will be along the lines of the code accepted by clang. – Praetorian Apr 11 '14 at 03:25
  • Okay, so it seems that sum() is clearly a dependent name. A dependent name is not looked up till the POI. But only ADL is done at the POI. Since an int is primitive, the ADL search space is empty. So this means clang++ is technically being too generous here? – kec Apr 11 '14 at 03:29
  • @Praetorian: Okay, sounds good. (It seems my comments keep crossing yours.) – kec Apr 11 '14 at 03:32
  • @user3521733: Second phase lookup does not **only** do ADL, it only *adds* ADL (i.e. it won't add non-ADL names declared *after* the template definition), but it does not *hide* or *remove* in any sense names that are visible at the point of definition of the template. – David Rodríguez - dribeas Apr 11 '14 at 04:31
  • 2
    By extension, i am curious to know if this in `c++1y` would be valid, with automatic return type deduction : `template auto sum(const T& t, const P&... p) { return t + sum(p...); }`. I believe it is, but as only clang has c++1y support and you demonstrate it was too generous already, i cannot state only on [that](http://coliru.stacked-crooked.com/a/5d54a713191dd827) – galop1n Apr 11 '14 at 08:35
  • 1
    @galop1n Good thinking! Both [gcc and clang accept the code](http://coliru.stacked-crooked.com/a/01b41e6b17772e55) if you compile with `-std=c++1y` and rely on return type deduction. – Praetorian Apr 11 '14 at 18:47

1 Answers1

2

As this question's answer (provided by Praetorian) indicates, the declaration is only complete after the return type, and GCC is correct. I believe clang's behavior is also allowable, but it's not portable. The answer in the link gives a workaround using a traits class, and that will usually do the job, but it's somewhat awkward and can be error prone (since you have to construct the return type in a separate expression which may differ subtlely from the function expression). Another possible workaround involves making your function a static member of a class template (and then adding a free function that forwards to the static template member).

There is another workaround that you can consider, which is hinted at in your second example which works on both compilers. When you call sum() on A, you are applying a user-defined type as a parameter. This involves argument-dependent-lookup, which causes the template generation to look a second time for overloads of sum() in the namespace of A (which happens to be the global namespace, the same as sum()), and this allows it to find the variadic function template during instantiation.

Thus, if you can arrange for one of your arguments to always be a user-defined type requiring ADL, you can then rely on the second phase of overload resolution to find the variadic template after it's fully declared. So, perhaps something like this might meet your need:

namespace sum_impl {
    struct Dummy { };

    template <typename T>
    T const &sum_helper(Dummy, T const &v) { return v; }

    template <typename T, typename ... Ts>
    auto sum_helper(Dummy d, T const &v, Ts const &...params)
            -> decltype(v + sum_helper(d, params...)) {
        return v + sum_helper(d, params...);
    }

    template<typename... P>
    auto sum( P const &...p )
            -> decltype( sum_helper( Dummy{}, p... ) {
        return sum_helper( Dummy{}, p... );
    }
}
using sum_impl::sum;
Community
  • 1
  • 1
Adam H. Peterson
  • 4,511
  • 20
  • 28