Неоднозначная перегрузка - упорядочение шаблонов частичных функций с пакетами параметров
Рассмотрим следующий надуманный фрагмент кода:
template <class... > struct pack { };
template <class R, class T, class... Args>
int foo(pack<T>, Args...)
{
return sizeof(R);
}
template <class R, class T, class... Ts, class... Args>
int foo(pack<T, Ts...>, Args... args)
{
return foo<T>(pack<Ts...>{}, args...);
}
int main() {
// gcc: OK, clang: ambiguous
foo<int>(pack<int>{});
// gcc: ambiguous, clang: ambiguous
foo<int>(pack<int>{}, 0);
}
Оба gcc и clang принимают оба вызова, если 2-я перегрузка изменена, чтобы принять пакет из по меньшей мере 2 типов вместо пакета по меньшей мере одного типа:
template <class R, class T, class T2, class... Ts, class... Args>
int foo(pack<T, T2, Ts...>, Args... args)
{
return foo<T>(pack<T2, Ts...>{}, args...);
}
Если параметр не выводимого шаблона перемещается к параметру выведенного шаблона, то:
template <class... > struct pack { };
template <class R, class T, class... Args>
int foo(pack<R>, pack<T>, Args...)
{
return sizeof(R);
}
template <class R, class T, class... Ts, class... Args>
int foo(pack<R>, pack<T, Ts...>, Args... args)
{
return foo(pack<T>{}, pack<Ts...>{}, args...);
}
int main() {
// gcc ok with both, clang rejects both as ambiguous
foo(pack<int>{}, pack<int>{});
foo(pack<int>{}, pack<int>{}, 0);
}
Я ожидаю, что все вызовы будут в порядке в каждой версии этого. Каков ожидаемый результат приведенных выше примеров кода?
Ответы
Ответ 1
Теперь я верю, что clang правильно отклоняет, а gcc неверно принимать те формы, которые он делает. Здесь упрощенный пример:
template <class...> struct pack { };
// (1)
template <class T>
void foo(pack<T> ) { }
// (2)
template <class T, class... Ts>
void foo(pack<T, Ts...> ) { }
int main() {
foo(pack<int>{});
}
Обе перегрузки действительны, и выведение (2) из (1) выполняется успешно. Единственная проблема заключается в выводе (1) из (2). Сначала я думал, что нет... Но [temp.deduct.type]/9 заявляет:
Если P
имеет форму, содержащую <T>
или <i>
, то каждый аргумент P i соответствующего списка аргументов шаблона P
сравнивается с соответствующим аргументом A i соответствующего списка аргументов шаблона A
. [...] При частичном заказе (14.8.2.4), если A i первоначально было расширением пакета:
- если P
не содержит аргумент шаблона, соответствующий A i, тогда A i игнорируется;
Итак, когда мы синтезируем типы для <T, Ts...>
(скажем, <U, Xs...>
), мы выводим T=U
, а затем нет аргумента шаблона, соответствующего расширению пакета Xs...
, поэтому мы его игнорируем. Все неизмененные параметры шаблона преуспели в выводе шаблона, поэтому мы считаем вывод (1) из (2) успешным.
Так как вычет преуспевает в обоих направлениях, ни один шаблон функции считается более специализированным, чем другой, и вызов должен быть неоднозначным.
Я еще не представил отчет об ошибке, ожидая подтверждения от сообщества.
Ответ 2
Сначала упростим задачу и рассмотрим
template <class...> struct pack {};
template <class T>
void foo(pack<T>) {}
template <class T, class... Ts>
void foo(pack<T,Ts...>) {}
int main() {
foo(pack<int>{});
}
который clang жалуется и отказывается компилировать, утверждая, что есть
двусмысленность между void foo(pack<T>)
[с T=int
] и void foo(pack<T,Ts...>)
[с T=int
, Ts=<>
]. Подобные ситуации разрешаются с помощью частичного упорядочения перегруженных шаблонов функций, который по существу пытается найти наиболее специализированную перегрузку.
В данном случае я считаю следующее:
В случае привязки, если в одном шаблоне функции есть трейлинг-пакет параметров, а другой нет, тот с пропущенным параметром считается более специализированным, чем тот, у которого есть пустой пакет параметров.
Таким образом, кажется, что первое должно быть предпочтительным, а clang было неправильным.