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) {}
template<typename T, typename... Args> auto foo(Args&&... args)
is "more specialized" but I don't know how that interacts with variadic templates. – Legibility