Передать лямбда-выражение в лямбда-аргумент с ++ 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 = c ++ 11

Спасибо

18

Решение

Есть несколько вещей, которые нужно уточнить по вашему вопросу. Первый из которых что такое лямбда?

Лямбда-выражение — это простое выражение, из которого компилятор сгенерирует уникальный тип, который не может быть назван, и в то же время сгенерирует экземпляр типа. Когда вы пишете: [](int i) { std::cout << i; } компилятор сгенерирует для вас тип примерно:

struct __lambda_unique_name {
void operator()(int i) const { std::cout << i; }
};

Как видите, это не функция, но тип, который реализует operator() как const функция-член. Если лямбда-код сделал какой-либо захват, компилятор сгенерирует код для захвата значения / ссылок.

В качестве углового случая для лямбд, подобных описанным выше, где нет захваченного состояния, язык допускает преобразование лямбда-типа в указатель на функцию с сигнатурой operator() (минус this часть), поэтому лямбда, приведенная выше, может быть неявно преобразована в указатель на функцию, принимающую int и ничего не возвращая

void (*f)(int) = [](int i) { std::cout << i; }

Теперь, когда основы были изложены, в вашем коде у вас есть эта лямбда:

auto f = [x,y](double (func)(int)) -> double {func(0); return 0.0;};

Правила для параметров функций (которые также применяются к лямбдам) определяют, что аргумент не может иметь тип функция, таким образом, аргумент лямбды распадается на указатель на функцию (так же, как аргумент массива типов распадается на тип указателя):

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)> может быть инициализирован с любым подлежащий выкупу объект с соответствующей подписью, он будет принимать лямбда-выражения в приведенном ниже коде за счет стирания типа, что обычно подразумевает динамическое распределение и динамическую диспетчеризацию.

Кроме того, вы можете отбросить синтаксический сахар и свернуть первый лямбда-эквивалент вручную, но сделать его универсальным. В этом случае, если лямбда проста, это может быть допустимым вариантом:

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;});

Наконец, если вы достаточно терпеливы, вы можете дождаться C ++ 14, где (скорее всего, он еще не ратифицирован) будет поддерживаться полиморфные лямбды которые упрощают создание вышеуказанного класса:

auto f = [](auto fn) { fn(0.0); return 0.0; } // unrolls to 'mylambda' above
31

Другие решения

Попробуйте использовать 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;});
}
5

Возможно, вам придется просто прикусить пулю и реализовать свои собственные функторы, как мы это делали в темные века:

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;});
}

Это должно поддерживать вас, пока ваш компилятор не поддерживает общие лямбда-выражения C ++ 14.

3

Например, вы можете попробовать что-то вроде следующего, если заранее знаете тип лямбды:

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 */ };
1

Вы можете указать захват лямбда, но это решение имеет свои ограничения:

#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;}));
}

Нажмите для рабочего примера.

0
По вопросам рекламы [email protected]