Передача лямбда-выражения на лямбда-аргумент С++ 11 - программирование
Подтвердить что ты не робот

Передача лямбда-выражения на лямбда-аргумент С++ 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

Спасибо

4b9b3361

Ответ 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;}));
}

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