Когда пакеты параметров шаблона выводятся как пустые?

Рассмотрим следующие примеры (Coliru link):

template <class... T> struct S { using type = int; };

template <class... T>
void f(typename S<T...>::type) {
    static_assert(sizeof...(T) == 0);
}

template <class... T>
void g(typename S<T...>::type, S<T...>*) {}

int main() {
    f(42);
    g(42, nullptr);
}

GCC и Clang оба довольны звонком в f, но не звонком в g.

В вызове к f, хотя T... появляется в невыбранном контексте, в конечном итоге он выводится как пустой. Похоже, это связано с [temp.arg.explicit]/4:

... Пакет параметров конечного шаблона ([temp.variadic]), не выведенный иначе, будет выведен как пустая последовательность аргументов шаблона....

Однако в обращении к g тот факт, что T... дополнительно появляется в выводимом контексте, что приводит к попытке выведения и неудаче, кажется, приводит к тому, что g становится нежизнеспособным. Похоже, что "отступление" к T... пусто после попытки и неудачи удержания.

  • Это поведение предназначено? Если так, почему?
  • Если да, то предполагалось ли, что формулировка "не выводится иначе" определяет это поведение? (т.е. это означает, что пустой запасной вариант возникает только в том случае, если пакет отображается без выводимых контекстов)
  • Если да, достаточно ли ясна эта формулировка? Кажется правдоподобным альтернативное прочтение слова "не выведено иначе": "либо дедукция не была сделана, либо попытка дедукции была неудачной".

Ответ 1

  ... Пакет параметров конечного шаблона ([temp.variadic]) не иначе выводится как пустая последовательность аргументов шаблона....

Может быть достаточно сказать, что не выведенное иное не является пунктом, который должен каким-то образом или автоматически ослаблять некоторые другие правила, или на самом деле это не имеет никакого отношения к тому, почему он искажен (вопреки тому, что, я думаю, вы подразумеваете).

Возможно, это другое правило лучше всего продемонстрировать на другом простом примере:

template<class T>
void f(T, T){};

int main() {
    f(int{42},short{42});
}

Выше не удается скомпилировать. Почему? потому что даже если им разрешено конвертировать short в int без проблем (продвижение), они не относятся к одному типу.

Кроме того, поскольку nullptr просто имеет несколько простой тип std::nullptr_t - он совсем не подходит для участия в выводе аргументов шаблона.

Так что на мгновение забудьте о невыбранном контексте и попробуйте с выведенным:

template <class... T>
void g(S<T...>*, S<T...>* ) {}

int main() {
    S<> s1;
    g(&s1, nullptr);
}

или если вы предпочитаете просто

int main() {
    S<> s1;
    g(&s1, 0);
}

и оба не удаются по одной и той же причине.

Теперь, если вы хотите разрешить преобразование - тогда используйте шаблон удостоверения - и это работает даже для невосстановленного контекста!

Для вашего случая пример может выглядеть так ():

template <class... T>
void g(typename S<T...>::type, std::type_identity_t<S<T...> >*) {}

int main() {
    f(42);
    g(42, nullptr);
} 

Который действителен. (обратите внимание, если у вас нет , просто напишите шаблон идентификации самостоятельно)

Как указано в комментарии, поворот вопроса может привести к более интересному вопросу?

В чем причина, позволяющая выводить аргументы пустых шаблонов в не выводимых контекстах?