Reputation: 717
The following code is a textbook example of a recursive variadic function overload. In both clang and GCC, it compiles cleanly, and main
returns 36 (as expected):
template <typename T>
int add(T val)
{
return val;
}
template <typename FirstTypeT, typename... RestT>
int add(FirstTypeT first_value, RestT... rest)
{
return first_value + add<RestT...>(rest...);
}
int main(void)
{
return add(12, 12, 12);
}
However, here is a slight modification. It uses a dependent type in the template definition instead of the template parameter directly:
struct Foo
{
using SomeType = int;
};
template <typename T>
int add(typename T::SomeType val)
{
return val;
}
template <typename FirstT, typename... RestT>
int add(typename FirstT::SomeType first_value, typename RestT::SomeType... rest)
{
return first_value + add<RestT...>(rest...);
}
int main(void)
{
return add<Foo, Foo, Foo>(12, 12, 12);
}
It compiles and runs as intended using GCC 5.2, but fails using clang 3.8:
clang++ variadic.cpp -o var -std=c++11 -Wall
variadic.cpp:15:26: error: call to 'add' is ambiguous
return first_value + add<RestT...>(rest...);
^~~~~~~~~~~~~
variadic.cpp:15:26: note: in instantiation of function template specialization 'add<Foo, Foo>' requested here
return first_value + add<RestT...>(rest...);
^
variadic.cpp:20:12: note: in instantiation of function template specialization 'add<Foo, Foo, Foo>' requested here
return add<Foo, Foo, Foo>(12, 12, 12);
^
variadic.cpp:7:5: note: candidate function [with T = Foo]
int add(typename T::SomeType val)
^
variadic.cpp:13:5: note: candidate function [with FirstT = Foo, RestT = <>]
int add(typename FirstT::SomeType first_value, typename RestT::SomeType... rest)
^
1 error generated.
My question is twofold.
typename RestT::SomeType...
?RestT = <>
)Upvotes: 9
Views: 855
Reputation: 60979
Current wording is quite clear on this: The parameter pack is completely ignored during partial ordering, because there are no arguments for it ([temp.deduct.partial]/(3.1)). [temp.func.order]/5 also gives a very on point example, even with deducible template arguments - indicating that your first example is also ambiguous:
[ Note: Since partial ordering in a call context considers only parameters for which there are explicit call arguments, some parameters are ignored (namely, function parameter packs, parameters with default arguments, and ellipsis parameters). [...] [ Example:
template<class T, class... U> void f(T, U ...); // #1 template<class T > void f(T ); // #2 void h(int i) { f(&i); // error: ambiguous // [...] }
However, this is not optimal. There is core issue 1395 on variadic template partial ordering:
CWG agreed that the example should be accepted, handling this case as a late tiebreaker, preferring an omitted parameter over a parameter pack.
(Issue 1825 gives a more refined strategy.) Both compilers implement this rule for the first case; Only GCC does for the second one (i.e. can be considered half a step ahead).
Upvotes: 7
Reputation: 118
The error message has already shown the reason.
When generate add(12), there are two available template functions. That's
template <typename T>
int add(typename T::SomeType val);
and
template <typename FirstT, typename... RestT>
int add(typename FirstT::SomeType first_value, typename RestT::SomeType... rest);
// and RestT is empty here(RestT = <>)
This is not a standard usage and clang is correct.
Consider this code.
#include <tuple>
#include <type_traits>
struct Foo
{
using SomeType = int;
};
// helper function to sum a tuple of any size
template<typename Tuple, std::size_t N>
struct TupleSum {
typedef typename std::tuple_element<N - 1, Tuple>::type ref_t;
typedef typename std::remove_reference<ref_t>::type noref_t;
static noref_t sum(const Tuple& t)
{
return std::get<N - 1>(t) + TupleSum<Tuple, N - 1>::sum(t);
}
};
template<typename Tuple>
struct TupleSum<Tuple, 1> {
typedef typename std::tuple_element<0, Tuple>::type ref_t;
typedef typename std::remove_reference<ref_t>::type noref_t;
static noref_t sum(const Tuple& t)
{
return std::get<0>(t);
}
};
template <typename... RestT>
int add(typename RestT::SomeType... rest) {
typedef decltype(std::forward_as_tuple(rest...)) tuple_t;
return TupleSum<tuple_t, sizeof...(RestT) >::sum(std::forward_as_tuple(rest...));
}
int main(void)
{
return add<Foo, Foo, Foo>(12, 12, 12);
}
Upvotes: 0