Передача лямбда-выражения на лямбда-аргумент С++ 11
Я хотел бы сделать что-то вроде этого:
int main()
{
auto f = [/*some variables*/](/*take lambda function*/)
{/*something with lambda function*/};
f([/*other variables*/](/*variables to be decided by f()*/)
{/*something with variables*/});
}
Я знаю, что можно передать лямбду на функцию, а также на лямбду.
Следующие работы:
int main()
{
int x=0;
int y=0;
auto f = [x,y](double (func)(int)) -> double
{func(0); return 0.0;};
f([](int i) -> double
{return 0.0;});
}
Но следующее не работает (как только я изменяю переменные области для добавления [x])
int main()
{
int x=0;
int y=0;
auto f = [x,y](double (func)(int)) -> double
{func(0); return 0.0;}
f([x](int i) -> double //[x] does not work
{return 0.0;});
}
который дает ошибку:
error: function "lambda [](double (*)(int))->double::operator()" cannot be called with the given argument list
argument types are: (lambda [](int)->double)
object type is: lambda [](double (*)(int))->double
У кого-нибудь есть идея о том, как это исправить, или об этом?
Я использую компилятор Intel
icpc (ICC) 13.1.2 с std = С++ 11
Спасибо
Ответы
Ответ 1
Есть несколько вещей, чтобы прояснить ваш вопрос. Первая из них - это лямбда?
Лямбда-выражение - это простое выражение, из которого компилятор будет генерировать уникальный тип, который нельзя назвать, и в то же время он будет генерировать экземпляр типа. Когда вы пишете: [](int i) { std::cout << i; }
, компилятор будет генерировать для вас примерно такой тип:
struct __lambda_unique_name {
void operator()(int i) const { std::cout << i; }
};
Как вы можете видеть, это не функция, а тип, который реализует operator()
как функцию члена const
. Если лямбда сделала какой-либо захват, компилятор будет генерировать код для захвата значения/ссылок.
В качестве углового случая для lambdas, как указано выше, где нет состояния, которое захватывается, язык допускает преобразование из лямбда-типа в указатель на функцию с сигнатурой operator()
(минус this
part), поэтому приведенная выше лямбда может быть неявно преобразована в указатель на функцию, принимающую int
и ничего не возвращающую:
void (*f)(int) = [](int i) { std::cout << i; }
Теперь, когда были изложены основы, в вашем коде у вас есть эта лямбда:
auto f = [x,y](double (func)(int)) -> double {func(0); return 0.0;};
Правила для параметров для функций (которые также применимы к lambdas) определяют, что аргумент не может иметь функцию типа, поэтому аргумент lambda распадается на указатель на функцию (таким же образом, что аргумент массива типов распадается к типу указателя):
auto f = [x,y](double (*func)(int)) -> double {func(0); return 0.0;};
В дальнейшем вы пытаетесь передать лямбду, у которой есть захват в качестве аргумента. Поскольку есть захват, специальное правило не применяется, и лямбда не конвертируется в указатель на функцию, давая ошибку компилятора, которую вы видите.
В текущем стандарте вы можете пойти одним из двух способов. Вы можете использовать стирание типа для удаления точного типа вызываемого объекта из подписи:
auto f = [x,y](std::function<double(int)> func) -> double {func(0); return 0.0;};
Поскольку std::function<double(int)>
может быть инициализирован любым вызываемым объектом с соответствующей сигнатурой, это будет принимать lambdas в коде ниже, за счет стирания типа, которое обычно подразумевает динамическое распределение и динамическую отправку.
В качестве альтернативы вы можете отказаться от синтаксического сахара и вручную свернуть первый лямбда-эквивалент, но сделать его общим. В этом случае, когда лямбда проста, это может быть допустимым вариантом:
struct mylambda {
template <typename F>
double operator()(F fn) const {
fn(0); return 0.0;
}
} f;
// then use the non-lambda as you tried:
f([x](int i) -> double {return 0.0;});
Наконец, если вы достаточно терпеливы, вы можете дождаться С++ 14, где (скорее всего, он еще не был ратифицирован) будет поддержка полиморфных лямбда, которые упростят создание вышеуказанного класса:
auto f = [](auto fn) { fn(0.0); return 0.0; } // unrolls to 'mylambda' above
Ответ 2
Попробуйте использовать std:: function:
#include <functional>
int main()
{
int x=0;
int y=0;
auto f = [x,y](std::function<double(int)> func) -> double
{func(0); return 0.0;};
f([x](int i) -> double {return 0.0;});
}
Ответ 3
Возможно, вам придется просто укусить пулю и реализовать свои собственные функторы, как в темноте:
struct F {
int x;
int y;
F(int x_, int y_) : x(x_), y(y_) {}
template <typename G>
double operator() (G&& g) const {
g(0);
return 0.0;
}
};
#include <iostream>
int main()
{
int x = 0;
int y = 0;
auto f = F(x, y);
f([x](int i){return 0.0;});
f([](int i){std::cout << i << std::endl;});
}
Это должно продолжаться, пока ваш компилятор не поддерживает С++ 14 generic lambdas.
Ответ 4
Вы можете попробовать что-то вроде следующего, если вы заранее знаете тип лямбда, например:
int main()
{
int x = 0, y = 0;
auto f = [x]( int i )->double {
return (double)x;
};
auto f2 = [x,y]( decltype(f) func )->double {
return func( 0 );
};
f2( f );
return 0;
}
Или альтернативу вы можете использовать библиотеку <functional>
для более общего решения, например:
auto f = [x,y]( std::function<double(int)> func ) { /* Do stuff */ };
Ответ 5
Вы можете очистить захват лямбда, но это решение имеет свои ограничения:
#include <new>
#include <utility>
namespace
{
template <typename F, int I, typename L, typename R, typename ...A>
inline F cify(L&& l, R (*)(A...) noexcept(noexcept(
std::declval<F>()(std::declval<A>()...))))
{
static L l_(std::forward<L>(l));
static bool full;
if (full)
{
l_.~L();
new (static_cast<void*>(&l_)) L(std::forward<L>(l));
}
else
{
full = true;
}
return [](A... args) noexcept(noexcept(
std::declval<F>()(std::forward<A>(args)...))) -> R
{
return l_(std::forward<A>(args)...);
};
}
}
template <typename F, int I = 0, typename L>
inline F cify(L&& l)
{
return cify<F, I>(std::forward<L>(l), F());
}
int main()
{
int x=0;
int y=0;
auto f = [x,y](double (func)(int)) -> double
{func(0); return 0.0;};
f(cify<double(*)(int i)>([x](int i) -> double //works now
{return 0.0;}));
}
Нажмите для рабочего примера.