После некоторых испытаний и с использованием упомянутой ссылки на стандарт: [temp.func.order] , [temp.deduct.partial] я пришел к следующему пониманию ситуации.
Проблема
Учитывая пример, приведенный в вопросе:
template<typename T, typename... Args> auto foo(Args&&... args) {} //#1
template<typename... Args> auto foo(Args&&... args) {} //#2
# 2 - это функция с пакетом переменных параметров, который может быть выведен. можно быть выведено, а не должно . Таким образом, ничто не мешает пользователю явно указать аргументы шаблона.
Следовательно, foo<char>('a')
может быть как явным воплощением # 2, так и воплощением # 1, провоцируя двусмысленность. Стандарт не поддерживает предпочтительное соответствие между перегрузкой № 1 и № 2.
GCC вышел за рамки стандарта в своей реализации, приписав более высокое предпочтение для # 1, когда аргумент шаблона передается вручную, в то время как Clang и MSVC оставили его ванильным.
Кроме того, двусмысленность появляется только тогда, когда первые аргументы из пакета переменных и T преобразуются в точно такой же тип.
Решение
Вот решения, которые я нашел для моего варианта использования. (Форвардное строительство объекта или разнообразная пачка объектов)
Вариант 1
Объявите дополнительную функцию, специализирующуюся на одном аргументе, она будет иметь приоритет над переменными. (Не масштабируется и не обобщается)
template<typename T> auto foo(T&& args) {}
//or
template<typename T, typename Arg> auto foo(Arg&& arg) {}
Вариант 2
Отключить перегрузку, когда первый аргумент непустого пакета параметров совпадает с заданным типом 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) {}