Рассмотрим следующие примеры (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);
}
и оба не удаются по одной и той же причине.
Теперь, если вы хотите разрешить преобразование - тогда используйте шаблон удостоверения - и это работает даже для невосстановленного контекста!
Для вашего случая пример может выглядеть так (c++2a):
template <class... T>
void g(typename S<T...>::type, std::type_identity_t<S<T...> >*) {}
int main() {
f(42);
g(42, nullptr);
}
Который действителен. (обратите внимание, если у вас нет c++2a, просто напишите шаблон идентификации самостоятельно)
Как указано в комментарии, поворот вопроса может привести к более интересному вопросу?
В чем причина, позволяющая выводить аргументы пустых шаблонов в не выводимых контекстах?