Ошибка замены с `std :: function` и ранее выведенным параметром шаблона - почему? - PullRequest
12 голосов
/ 27 сентября 2019

Рассмотрим следующий код:

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.

Почему это не так? Я предполагаю, что порядок, в котором происходит замещение / вычет, как-то связан с этим, но я хотел бы увидеть соответствующую Стандартную формулировку.

Бонусный вопрос: это то, что потенциально может быть изменено в будущем стандарте при сохранении обратной совместимости, или есть фундаментальная причина, по которой этот вид замены не работает?

Ответы [ 2 ]

13 голосов
/ 27 сентября 2019

Хотя я понимаю, что, как правило, тип параметра std :: function не может быть выведен, поскольку он является не выводимым контекстом

Это не выводимый контекст,Наоборот.Поскольку выполняется попытка вычета для параметра std::funcition, а аргументом является , а не a 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 успешно выводится из первого аргумента функции, и нечего выводить.Таким образом, вычет считается успешным.

Live

7 голосов
/ 27 сентября 2019

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...