Как передать и выполнить анонимную функцию как параметр в С++ 11?

Код, который я ищу, похож на следующий.

bool Func1(int Arg1, C++11LambdaFunc Arg2){
    if(Arg1 > 0){
        return Arg2(Arg1);
    }
}

Позже я буду использовать этот код.

Func1(12, [](int D) -> bool { ... } );

Ответ 1

Базовая версия для использования в файле заголовка:

template<typename Lambda>
bool Func1(int Arg1, Lambda Arg2){ // or Lambda&&, which is usually better
  if(Arg1 > 0){
    return Arg2(Arg1);
  } else {
    return false; // remember, all control paths must return a value
  }
}

Более сложная версия, если вы хотите разделить свой интерфейс с вашей реализацией (у нее есть затраты времени на запуск):

bool Func1(int Arg1, std::function<bool(int)> Arg2){
  if(Arg1 > 0){
    return Arg2(Arg1);
  } else {
    return false; // remember, all control paths must return a value
  }
}

std::function использует стирание типа для создания созданной пользователем обертки вокруг вашей лямбда, а затем предоставляет не виртуальный интерфейс, который использует шаблон pImpl для пересылки его в созданный по умолчанию обертку. 1

Или, менее технически, std::function<bool(int)> - это класс, который может обернуть почти все, что вы можете назвать как функция, передавая один параметр, совместимый с передачей int, и возвращает то, что совместимо с возвращая a bool.

Вызов через std::function имеет стоимость времени выполнения, примерно равную вызову функции virtual (вызванному стиранием стираемого типа), а при его создании он должен скопировать состояние объекта функции (aka функтор) (которые могут быть дешевыми - безъядерными лямбдами или лямбдами, захватывающими аргументы по ссылке - или дорогостоящими в некоторых других случаях) и хранить их (как правило, в свободном магазине или куче, которая имеет стоимость), тогда как чистая -template версии могут быть "inlined" в точке вызова (т.е. может стоить не только меньше, чем вызов функции, но и компилятор может оптимизировать вызов функции и вернуть границы!)

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

template<typename Lambda>
bool Func1(int Arg1, Lambda&& Arg2){
  if(Arg1 > 0){
    return std::forward<Lambda>(Arg2)(Arg1);
  } else {
    return false; // remember, all control paths must return a value
  }
}

который использует технику, известную как "совершенная пересылка". Для некоторых функторов это порождает несколько другое поведение, чем # 1 (и обычно более правильное поведение).

Большая часть усовершенствований приходит из использования && в списке аргументов: это означает, что ссылка на функтор передается (вместо копии), сохраняя некоторые затраты и позволяя как const, так и не const, который должен быть передан.

Изменение std::forward<Lambda>(...) приведет только к изменению поведения, если кто-то использует относительно новую функцию С++, которая позволяет методам (включая operator()) переопределять статус rvalue/lvalue указателя this. Теоретически это может быть полезно, но число функторов, которые я видел, которые фактически переопределяют на основе состояния rvalue this, равно 0. Когда я пишу серьезный библиотечный код (tm), я иду на эту тему, но редко в противном случае.

Есть еще одна возможность рассмотреть. Предположим, вы хотите взять либо функцию, которая возвращает bool, либо функцию, которая возвращает void, и если функция возвращает void, вы хотите обработать ее, как если бы она вернула true. Например, вы выполняете функцию, которая вызывается при итерации по какой-либо коллекции, и вы хотите опционально поддерживать раннее завершение. Функция возвращает false, когда она хочет остановиться преждевременно, и true или void в противном случае.

Или, в более общем случае, если у вас есть несколько переопределений функции, одна из которых принимает функцию, а другие принимают другой тип в одном месте.

Это возможно, насколько я собираюсь войти здесь (либо с помощью смарт-адаптера, либо с помощью методов SFINAE). Однако вам, вероятно, лучше просто создать две разные именованные функции, потому что требуемые методы слишком тяжелы.


1 Технически std::function может использовать волшебную волшебную пыль, чтобы делать то, что она делает, поскольку ее поведение описывается стандартом, а не его реализацией. Я описываю простую реализацию, которая аппроксимирует поведение реализации std::function, с которой я взаимодействовал.

Ответ 2

Первое решение:

Вы можете сделать вашу функцию Func1() шаблоном функции:

template<typename T>
bool Func1(int Arg1, T&& Arg2){
    if(Arg1 > 0){
        return Arg2(Arg1);
    }

    return false; // <== DO NOT FORGET A return STATEMENT IN A VALUE-RETURNING
                  //     FUNCTION, OR YOU WILL GET UNDEFINED BEHAVIOR IF FLOWING
                  //     OFF THE END OF THE FUNCTION WITHOUT RETURNING ANYTHING
}

Затем вы можете вызвать его, как хотите:

int main()
{
    Func1(12, [](int D) -> bool { return D < 0; } );
}

Второе решение:

Если вы не хотите использовать шаблоны, альтернативой (которая принесет некоторые издержки во время выполнения) является использование std::function:

#include <functional>

bool Func1(int Arg1, std::function<bool(int)> Arg2){
    if(Arg1 > 0){
        return Arg2(Arg1);
    }

    return false;
}

Еще раз, это позволит вам вызвать Func1() так, как вы пожелаете:

int main()
{
    Func1(12, [](int D) -> bool { return D < 0; } );
}

Ответ 3

Для тех, чьи вкусы более традиционны, обратите внимание на то, что не захватывающие lambdas могут преобразовывать в указатели на функции. Таким образом, вы можете написать свою функцию выше:

bool Func1(int Arg1, bool (*Arg2)(int)) { ... }

И он будет корректно работать как для традиционных функций , так и lambdas.