Ambiguous call when recursively calling variadic t

2020-02-09 10:45发布

问题:

Consider this piece of code:

template<typename FirstArg>
void foo()
{
}

template<typename FirstArg, typename... RestOfArgs>
void foo()
{
    foo<RestOfArgs...>();
}

int main()
{
    foo<int, int, int>();
    return 0;
}

It does not compile due to ambiguous call foo<RestOfArgs...>(); when RestOfArgs has only one element ({int}).

But this compiles without error:

template<typename FirstArg>
void foo(FirstArg x)
{
}

template<typename FirstArg, typename... RestOfArgs>
void foo(FirstArg x, RestOfArgs... y)
{
    foo(y...);
}

int main()
{
    foo<int, int, int>(5, 6, 7);
    return 0;
}

Why is there ambiguity in the first case?

Why is there no ambiguity in the second case?

回答1:

In Function template overloading

There is lot of rules to tell which template functions is more specialized (according to given parameters).

The point which makes

template<typename> void foo();
template<typename, typename...> void foo();

ambiguous for foo<int>(), but not

template<typename T> void foo(T);
template<typename T, typename... Ts> void foo(T, Ts...);

for foo(42) is the following:

In case of a tie, if one function template has a trailing parameter pack and the other does not, the one with the omitted parameter is considered to be more specialized than the one with the empty parameter pack.



回答2:

Why is there ambiguity in the first case?

RestOfArgs can be empty.

So foo<int> can be instantiated as:

template<int>
void foo()
{
}

and

template<int,>
void foo()
{
    foo<>();
}

both will compile, so it is ambiguous.

Actually foo<>() won't compile, but it fails in the next instantiation, so it doesn't matter.

Why is there no ambiguity in the second case?

foo<int>(7) can be instantiated as:

template<int>
void foo(int 7)
{
}

and

template<int>
void foo(int 7)
{
    foo();
}

but the second one is an error, because there are no foo taking no argument, so the only candidate is the first one, so there won't be ambiguous



回答3:

The answer by @ZangMingJie answers the difference in behavior your are observing in your code.

I found it easier to understand the name resolution with the following change:

template<typename FirstArg>
void foo()
{
    printf("1\n");
}

template<typename FirstArg, typename SecondArg, typename... RestOfArgs>
void foo()
{
    printf("2\n");
    foo<SecondArg, RestOfArgs...>();
}

int main()
{
    foo<int, int, int>();
    return 0;
}

When there are two or more template parameters are used, the second function gets invoked. When there is one template parameters, the first function gets invoked.