Когда вы вызываете шаблон вашей функции, например, вот так
run_callback(printint, 1);
, компилятор попытается вывести аргументы шаблона, которые при замене на типы параметров функции приведут типы параметров функции к типамаргументов.Проблема здесь в том, что нет Args...
, который вы могли бы вставить в const std::function<void(Args...)>&
, чтобы этот тип соответствовал типу printint
, который равен void(int)
.Следовательно, вывод аргумента шаблона завершается неудачей.
Если вы просто хотите, чтобы ваш run_callback
использовал любую функцию с сигнатурой, совпадающей с Args...
, попросите ее обратиться к функции с такой сигнатурой:
template <typename... Args>
void run_callback(void (&func)(Args...), Args... as)
{
}
живой пример
Это, однако, будет несколько хрупким, поскольку в основном требуется, чтобы типы в Args
были точными совпадениями для типов параметров обратного вызова.Скорее всего, вы бы хотели, чтобы run_callback
работал с любой функцией (или, в более общем смысле, с любой вызываемой функцией), которая может быть вызвана с указанным args
.Один из способов добиться этого - сделать так, чтобы ваш шаблон функции принимал любой тип в качестве обратного вызова, но включал перегрузку только тогда, когда параметр func
на самом деле вызывается и вызывается с использованием соответствующего списка аргументов:
template <typename F, typename... Args>
auto run_callback(F&& f, Args&&... as) -> std::enable_if_t<std::is_invocable_v<F, Args...>>
{
f(std::forward<Args>(as)...); // call the callback
}
живой пример
Наконец, если по какой-то причине вам действительно нужен параметр func
, то есть std::function
, вы можете скрыть пакет параметров Args...
в func
в не выведенном контексте :
template <typename... Args>
void run_callback(const std::common_type_t<std::function<void(Args...)>>& func, Args... as)
{
}
живой пример
Хитрость здесь заключается в использовании std::common_type_t<T>
, которыйна самом деле это просто сокращение для std::common_type<T>::type
, которое в итоге просто снова станет T
(Примечание: в std::common_type
нет ничего особенного, за исключением того, что он уже есть для нас; мы могли бы использовать любого другого помощникашаблон; все, что нам нужно, это то, что передает свой аргумент зависимому имени).В общем, невозможно однозначно определить, какой T
вам нужно подключить к C<T>::xyz
, чтобы C<T>::xyz
стал определенным типом.Даже не гарантируется, что существует xyz
для каждого T
или что C<T>::xyz
будет типом для начала.По этой причине спецификатор вложенного имени в квалифицированном-идентификаторе определен в качестве невведенного контекста [temp.deduct.type] /5.1.Короче говоря, это означает, что Args...
теперь появляется в типе параметра func
таким образом, что компилятор не будет пытаться определить, что Args...
должно быть из аргумента, переданного для параметра func
(т.е. он не будет пытаться сделать то, что привело к сбою вашего исходного кода).Однако он все еще может вывести Args...
из пакета функциональных параметров as
.Таким образом, дедукция типов просто сделает Args...
теми типами аргументов, которые переданы для as...
, и будут успешными.После успешного вывода типа выведенные Args...
подставляются обратно в остальные параметры, и тип func
получится равным const std::function<void(Args...)>&
, но теперь с типами Args...
, взятыми из того, из чего они были выведены, изпакет параметров as
.
Другим способом сделать то же самое было бы, например, обернуть аргумент для первого параметра в списке инициализатора:
run_callback({ printint }, 1);
live example
Аргумент, который является списком инициализатора, также делает параметр не выводимым контекстом в этом случае [temp.deduct.type] /5.6, поэтому объяснениепочему это работает в основном так же, как в предыдущем примере.Обратите внимание, что, хотя предыдущий подход решает проблему из объявления шаблона, этот подход решает ее на месте вызова функции.