Reputation: 23
I wrote code for iteration over std::tuple using several approach (of curiosity). I successfully used std::enable_if approach, static class-function with template specialization, template function overloading, etc. For function overloading I wrote the following:
template<size_t N> struct SizeT{};
template<typename Tuple>
void print(Tuple, SizeT<0>){}
template<typename Tuple, size_t N>
void print(Tuple t, SizeT<N>)
{
print(t,SizeT<N-1>());
std::cout << std::get<N-1>(t) << ' ';
}
template<typename Tuple>
void print(Tuple t){
print(t, SizeT<std::tuple_size<Tuple>::value>());
std::cout << std::endl;
}
It's work fine. But I tried forward order in template recursion:
template<size_t N> struct SizeT{};
template<typename Tuple, size_t N>
void print(Tuple t, SizeT<N>) {
std::cout << std::get<N>(t) << ' ';
print(t,SizeT<N+1>());
}
template<typename Tuple>
void print(Tuple, SizeT< std::tuple_size<Tuple>::value >){
std::cout << std::endl;
}
template<typename Tuple>
void print(Tuple t) {
print(t,SizeT<0>());
}
There are compilation error about ambiguous call:
call of overloaded 'print(std::tuple<A<int>&, A<int>&, A<long long int>&, A<long int>&>&, forward_first::SizeT<4u>)' is ambiguous
print(t,SizeT<N+1>());
candidates are:
void forward_first::print(Tuple, forward_first::SizeT<N>) [with Tuple = std::tuple<A<int>&, A<int>&, A<long long int>&, A<long int>&>; unsigned int N = 4u]
void print(Tuple t, SizeT<N>)
void forward_first::print(Tuple, forward_first::SizeT<std::tuple_size<Tuple>::value>) [with Tuple = std::tuple<A<int>&, A<int>&, A<long long int>&, A<long int>&>]
void print(Tuple, SizeT< std::tuple_size<Tuple>::value >)
But if replace std::tuple_size::value by actual constant (e.g. 3 literal), then it's work:
template<size_t N> struct SizeT{};
template<typename Tuple, size_t N>
void print(Tuple t, SizeT<N>) {
std::cout << std::get<N>(t) << ' ';
print(t,SizeT<N+1>());
}
template<typename Tuple>
void print(Tuple, SizeT<3>){
std::cout << std::endl;
}
template<typename Tuple>
void print(Tuple t) {
print(t,SizeT<0>());
}
Why call with std::tuple_size::value ambiguous? Why function
template<typename Tuple>
void print(Tuple, SizeT< std::tuple_size<Tuple>::value >)
no more specialized then
template<typename Tuple, size_t N>
void print(Tuple t, SizeT<N>)
??
Upvotes: 2
Views: 386
Reputation: 70526
The problem is that you have a nested value
inside std::tuple_size
that acts surprisingly. A quick rule of thumb in template argument deduction is that anything after a ::
is opaque to the compiler. The surprising thing here is that it happens not during the argument deduction phase itself (both overloads have their own arguments properly deduced), but by the deduction of one overload's parameters from the other overload's synthesized arguments during the phase of overload resolution.
14.8.2.4 Deducing template arguments during partial ordering [temp.deduct.partial]
2 Two sets of types are used to determine the partial ordering. For each of the templates involved there is the original function type and the transformed function type. [ Note: The creation of the transformed type is described in 14.5.6.2. — end note ] The deduction process uses the transformed type as the argument template and the original type of the other template as the parameter template. This process is done twice for each type involved in the partial ordering comparison: once using the transformed template-1 as the argument template and template-2 as the parameter template and again using the transformed template-2 as the argument template and template-1 as the parameter template.
So given your two overloads
// #1
template<typename Tuple>
void print(Tuple, SizeT< std::tuple_size<Tuple>::value >)
// #2
template<typename Tuple, size_t N>
void print(Tuple t, SizeT<N>)
It is clear that the first has no 2nd template parameter to deduce and so it is at least as specialized as the second overload. The question is whether the second can have it's N
parameter deduced from the first overloads' synthesized 2nd argument.
The reason that argument deduction does not take place it is a non-deduced context:
14.8.2.5 Deducing template arguments from a type [temp.deduct.type]
5 The non-deduced contexts are:
— ...
— A non-type template argument or an array bound in which a subexpression references a template parameter.
— ...
This means that the second overload is also at least as specialized as the first one. Hence, overload resolution is not able to select one, and the program is ill-formed.
Note: a similar problem (with 3 overloads and ambiguities only for certain values of N
appears in this Q&A).
Upvotes: 1