c++templatesvariadic-templatesambiguous-call

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?


Solution

  • 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) {}