Ошибка замещения с помощью '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, и из-за этого, вы получаете ошибку.