Неоднозначная перегрузка - упорядочение шаблонов частичных функций с пакетами параметров

Рассмотрим следующий надуманный фрагмент кода:

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 было неправильным.