Этот вопрос является продолжением другого вопроса десятилетней давности:
#include<functional>
template <typename ReturnType, typename... ArgumentTypes>
struct Caller
{
static void Call(std::function<ReturnType(ArgumentTypes...)>);
};
template <typename ReturnType, typename...ArgumentTypes>
void Call(std::function<ReturnType(ArgumentTypes...)>f);
int main()
{
Caller<void>::Call([]() {});//Well-formed
Call<void>({[]() {}});//Well-formed
Call<void>([]() {});//Ill-formed
}
Код предоставляет три метода для расширения пакета параметров шаблона в сигнатуре std::function
. Первые два были протестированы как правильно сформированные и могут быть скомпилированы; последний такой же, как и старый вопрос, и поэтому имеет неправильную форму. Вопрос в том, каким образом первые два метода обходят проблему, вызывающую сбой компиляции в исходной задаче и последнем методе?
Для структуры Caller
передаются все аргументы шаблона (только void
и пустой пакет), лямбду можно преобразовать в std::function<void()>
.
@NathanOliver - предоставленная вами ссылка основана на том, что «лямбда-выражение не выводит параметры шаблона std::function, поскольку они являются несвязанными типами», что неверно. std::function
может выводить типы возвращаемых значений и аргументов из лямбды, если они не являются шаблонами (auto
)
@Gene Это можно сделать с помощью std::function foo = lambda
из-за CTAD. Это не работает, если параметры std::function
выводятся из параметра функции: coliru.stacked-crooked.com/a/4aceb8468800936c
@NathanOliver - Я не говорю, что вывод в этом конкретном случае не должен быть ошибочным, я говорю, что ответ в этой ссылке неверен.
@ Джин, мне придется с тобой не согласиться.
Для структуры Caller
передаются все аргументы шаблона (только void
и пустой пакет), поскольку частичных CTAD нет. Лямбду можно преобразовать в std::function<void()>
, так что всё в порядке.
Для шаблона функции Call
предоставленные аргументы являются только первыми, вычет все равно может произойти.
Один из способов остановить возможный вывод — сохранить в промежуточной переменной:
auto call = Call<void>; // void (*)()
call([](){}); // OK
лямбда не являются std::function
, поэтому их нельзя вывести как std::function<Ret(Args...)>
, поэтому третий метод не работает.
За второе дополнительная {..}
также останавливает вычет:
См. template_argument_deduction#Non-deduced_contexts #7 для более подробной информации:
Параметр P, где A представляет собой список инициализации в скобках, но P не является
std::initializer_list
, ссылку на него (возможно, с указанием cv) или ссылку на массив}}:
Поскольку {..}
не имеет типа, не должен ли вывод типа для Args...
потерпеть неудачу?
Изначально я думал то же самое о случае {..}
, но тогда он работает только с пустым пакетом параметров. Если вы измените аргумент на int, Call<void>({[](int) {}});
потерпит неудачу, а Call<void>(std::function([](int) {}));
увенчается успехом.
Я думаю, что причина наблюдаемого поведения заключается в том, что во всех этих случаях аргументы функции не выводятся. Явно специализированный Call<void>
раскрывается как имеющий пустой пакет, что означает, что совпадать может только int(void)
лямбда. При использовании std::function
приведения выводятся как тип возвращаемого значения, так и пакеты параметров.
Поскольку объяснение второго случая из этого ответа вычеркнуто, какое объяснение является правильным?
@埃博拉酱: Мне пришлось немного разобраться, чтобы найти правильный ответ.
Я не могу сказать, почему
Call<void>({[]() {}});//Well-formed
компилируется, аCall<void>([]() {});//Ill-formed
нет, потому что: stackoverflow.com/questions/53326206/…