Этот вопрос является продолжением другого вопроса десятилетней давности:
#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/…