Ошибка замещения с помощью 'std::function' и ранее выведенного параметра шаблона - почему?
Рассмотрим следующий код:
template <typename>
struct S { };
void g(S<int> t);
template <typename T>
void f(T, std::function<void(S<T>)>);
При попытке вызвать
f(0, g);
Я получаю следующую ошибку:
error: no matching function for call to 'f'
f(0, g);
^
note: candidate template ignored: could not match
'function<void (S<type-parameter-0-0>)>'
against 'void (*)(S<int>)'
void f(T, std::function<void(S<T>)>);
^
живой пример на godbolt.org
Хотя я понимаю, что, как правило, тип параметра std::function
не может быть выведен, поскольку это не выводимый контекст
В этом случае T
может быть сначала выведен с помощью переданного аргумента 0
, а затем подставлен в std::function<void(S<T>)>
, чтобы получить std::function<void(S<int>)>
.
Я ожидаю, что после вывода T=int
, компилятор заменит T
везде в сигнатуре, а затем попытается создать параметр std::function
с аргументом g
.
Почему это не так? Я предполагаю, что порядок, в котором происходит замещение/вычет, как-то связан с этим, но я хотел бы увидеть соответствующую стандартную формулировку.
Дополнительный вопрос: может ли это быть чем-то потенциально изменено в будущем стандарте при сохранении обратной совместимости, или есть фундаментальная причина, по которой этот вид замены не работает?
Ответы
Ответ 1
Хотя я понимаю, что, как правило, тип параметра std::function не может быть выведен, поскольку это не выводимый контекст.
Это не выводимый контекст. Наоборот. Поскольку делается попытка вычета для параметра std::function
, но аргумент не является std::function
, вычет не выполняется. Вычет аргументов шаблона из аргументов функции должен совпадать для всех аргументов функции. Если он потерпит неудачу, он потерпит неудачу полностью.
[temp.deduct.type]
2 В некоторых случаях вычет выполняется с использованием одного набора типы P и A, в остальных случаях будет набор соответствующих типы P и A. Вывод типа производится независимо для каждой пары P/A, и выведенные значения аргумента шаблона затем объединяются. Если тип вычет не может быть сделан для любой пары P/A, или если для любой пары вычет приводит к более чем одному возможному набору выводимых значений, или если разные пары дают разные выведенные значения, или если какой-либо шаблон аргумент не остается ни выведенным, ни явно заданным, шаблон вывод аргумента не удался.
Преобразование типа второго параметра функции в не выводимый контекст на самом деле позволяет преодолеть ошибку.
#include <functional>
template<typename T>
struct type_identity {
using type = T;
};
template <typename>
struct S { };
void g(S<int> ) {}
template <typename T>
void f(T, typename type_identity<std::function<void(S<T>)>>::type) {}
int main() {
f(0, g);
}
T
успешно выводится из первого аргумента функции, и нечего выводить. Таким образом, вычет считается успешным.
Жить
Ответ 2
Хотя я понимаю, что, как правило, тип параметра std::function
не может быть выведен, поскольку это не выводимый контекст, в этом случае T
может быть сначала выведен с помощью переданного аргумента 0
.
Это неправда. T
выводится в этом контексте. Если вы измените код на
template <typename T>
void f(std::function<void(S<T>)>);
int main()
{
f(std::function<void(S<int>)>(g));
}
код будет скомпилирован, и T
будет правильно выведен.
Ваша проблема в том, что вы передаете объект в функцию, из которой он не может извлечь T
. Компилятор не будет выполнять никакого преобразования аргументов функции, когда он попытается вывести T
. Это означает, что у вас есть int
и функция в качестве типов, передаваемых в функцию. Он получает int
из 0
, затем пытается получить тип из std::function
, который вы передаете во втором параметре, но, поскольку вы не передали std::function
, он не может извлечь T
, и из-за этого, вы получаете ошибку.