Вывод типа для нежизнеспособных шаблонов функций
В своем ответе на этот вопрос и в разделе комментариев Johannes Schaub - litb говорит, что при попытке сделать вывод типа шаблона для шаблона функции, который требует больше аргументов, чем было передано, возникает "ошибка соответствия":
template<class T>
void foo(T, int);
foo(42); // the template specialization foo<int>(int, int) is not viable
В контексте другого вопроса важно, является ли успешным вывод типа для шаблона функции (и происходит ли замена):
template<class T>
struct has_no_nested_type {};
// I think you need some specialization for which the following class template
// `non_immediate_context` can be instantiated, otherwise the program is
// ill-formed, NDR
template<>
struct has_no_nested_type<double>
{ using type = double; };
// make the error appear NOT in the immediate context
template<class T>
struct non_immediate_context
{
using type = typename has_no_nested_type<T>::type;
};
template<class T>
typename non_immediate_context<T>::type
foo(T, int) { return {}; }
template<class T>
bool foo(T) { return {}; }
int main()
{
foo(42); // well-formed? clang++3.5 and g++4.8.2 accept it
foo<int>(42); // well-formed? clang++3.5 accepts it, but not g++4.8.2
}
При создании первого шаблона функции foo
за T == int
, замена производит недопустимый тип не в непосредственном контексте foo
, Это приводит к серьезной ошибке (вот о чем связан этот вопрос.)
Однако при сдаче foo
вывести его аргумент шаблона, g++ и clang++ соглашаются, что создание экземпляра не происходит. Как объясняет Йоханнес Шауб, это происходит из-за "ошибки совпадения".
Вопрос: Что такое "ошибка соответствия", и где и как она указана в Стандарте?
Альтернативный вопрос: почему есть разница между foo(42)
а также foo<int>(42)
для g++?
Что я нашел / попробовал до сих пор:
[over.match.funcs]/7 и [temp.over] описывают особенности разрешения перегрузки для шаблонов функций. Последние, кажется, требуют замены шаблонных параметров для foo
,
Интересно, что [over.match.funcs]/7 запускает процесс, описанный в [temp.over], перед проверкой на жизнеспособность шаблона функции (специализация). Точно так же вычитание типа не учитывает, скажем, аргументы функции по умолчанию (кроме того, что делает их не выводимым контекстом). Насколько я могу судить, это не касается жизнеспособности.
Другим, возможно, важным аспектом является способ определения типа вывода. Он действует на параметры одной функции, но я не вижу, где проводится различие между типами параметров, которые содержат / зависят от параметров шаблона (например, T const&
) и те, которые не (как int
).
Тем не менее, g++ делает различие между явным указанием параметра шаблона (серьезная ошибка) и разрешением их вывода (ошибка вывода / SFINAE). Зачем?
1 ответ
Я подытожил процесс, описанный в 14.8.2.1p1
Вывод аргумента шаблона выполняется путем сравнения каждого типа параметра шаблона функции (назовите его P) с типом соответствующего аргумента вызова (назовите его A), как описано ниже.
В нашем случае мы имеем для P (T, int)
и для А мы имеем (int)
, Для первой пары P/A, которая T
против int
мы можем соответствовать T
в int
(согласно процессу, описанному в 14.8.2.5). Но для второй "пары" мы имеем int
но не имеют аналогов. Таким образом, вычет не может быть сделан для этой "пары".
Таким образом, в соответствии с 14.8.2.5p2: "Если выведение типа не может быть выполнено для любой пары P/A,..., выведение аргумента шаблона завершается неудачно".
Тогда вы никогда не дойдете до того момента, когда подставите аргументы шаблона в шаблон функции.
Все это, вероятно, может быть более точно описано в Стандарте (IMO), но я считаю, что именно так можно реализовать вещи, соответствующие реальному поведению Clang и GCC, и это кажется разумной интерпретацией Standardese.