Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ partial template argument deduction for function with variadic pack produces ambiguous call in Clang and MSVC

Consider the following snippet (available on compiler epxlorer):

template<typename T, typename... Args>
auto foo(Args&&... args) {}

template<typename... Args>
auto foo(Args&&... args) {}

int main() {
    foo<char>('a');
}

It compiles perfectly fine for GCC and fails for both Clang and MSVC (with compiler saying ambiguous call)

Why do Clang and MSVC fail to such seemingly obvious deduction?

EDIT: GCC provides me with the expected solution as a user, is there an easy way to push clang and msvc to choose the template without much change of the original code?

like image 792
Neelahn Avatar asked Jul 08 '19 23:07

Neelahn


2 Answers

If you examine the additional diagnostic lines from the compiler, you'll see that it says

<source>(6): note: could be 'auto foo<char>(char &&)'
<source>(3): note: or       'auto foo<char,char>(char &&)'

(from MSVC; Clang is similar)

In this case, since the first (only) parameter to the function foo is a char, the compiler cannot distinguish between the one template parameter and two template parameter versions of the template.

If you change your function call to

foo<char>(10);

it will compile.

There is an example in the language spec ("Partial ordering of function templates", [temp.func.order]) very similar to your code:

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
}

Since GCC compiles it, this is a bug in GCC.

like image 98
1201ProgramAlarm Avatar answered Sep 22 '22 00:09

1201ProgramAlarm


After some tests, and using the mentioned reference to the standard: [temp.func.order], [temp.deduct.partial], I came to the following understanding of the situation.

Problem

Considering the example given in the question:

template<typename T, typename... Args> auto foo(Args&&... args) {} //#1

template<typename... Args>             auto foo(Args&&... args) {} //#2

#2 is a function with a variadic parameter pack that can be deduced. can be deduced, not have to. Thus, nothing prevents the user to explicitly specify the template arguments. Therefore, foo<char>('a') can be as much an explicit instantiation of #2 as an instantiation of #1, provoking the ambiguity. The standard does not favor a preferred match between the overload #1 and #2.

GCC went beyond the standard within its implementation by attributing a higher preference for #1 when a template argument is manually given, while Clang and MSVC kept it vanilla.

Furthermore, ambiguity appears only when the first arguments from the variadic pack and T resolve to the exact same type.

Solution

Here are the solutions that I found for my use case. (Forward object construction or a variadic pack of objects)

Variant 1

Declare an extra function specializing for one argument, this would take precedence over the variadic-based ones. (Does not scale or generalize)

template<typename T> auto foo(T&& args) {}
//or
template<typename T, typename Arg> auto foo(Arg&& arg) {}

Variant 2

Disable the overload when the first argument of the non-empty parameter pack is same as the given type T.

template<typename T, typename... Args>
constexpr bool is_valid() {
    if constexpr(sizeof...(Args)==0)
        return true;
    else
        return !std::is_same_v<T,std::tuple_element_t<0,std::tuple<Args...> > > ;
}

template<typename T, typename... Args, typename = std::enable_if_t<is_valid<T,Args...>()> >
auto foo(Args&&... args) {}
like image 40
Neelahn Avatar answered Sep 21 '22 00:09

Neelahn