Какой способ вызова lambdas в С++ является самым идиоматическим?

Я заметил, что lambdas работают с указателями функций, а также с выделенным типом function, используя g++.

#include <functional>

typedef int(*fptr)(int, int);

int apply_p(fptr f, int a, int b) {
  return f(a, b);
}

int apply_f(std::function<int(int, int)> f, int a, int b) {
  return f(a, b);
}

void example() {
  auto add = [](int a, int b) -> int {
    return a + b;
  };

  apply_p(add, 2, 3); // doesn't give an error like I'd expect it to
  apply_f(add, 2, 3); 
}

Мой вопрос: какие из них наиболее идиоматичны? И каковы опасности и/или преимущества использования одного над другим?

Ответ 1

Я заметил, что lambdas работают с использованием указателей функций, а также выделенного типа function

Если лямбда ничего не захватывает, тогда она может распадаться на указатель функции.

Какие из них наиболее идиоматичны?

Ни. Используйте function, если вы хотите сохранить произвольный вызываемый объект. Если вы просто хотите создать и использовать его, сохраните его общий:

template <typename Function>
int apply(Function && f, int a, int b) {
     return f(a,b);
}

Вы можете пойти дальше и задать тип и тип возвращаемого и аргументального; Я оставлю это как упражнение.

И каковы опасности и/или преимущества использования одного над другим?

Версия указателя функций работает только с (не-членными или статическими) функциями и не захватывает lambdas и не позволяет передавать какое-либо состояние; только сама функция и ее аргументы. Тип function может обернуть любой вызываемый тип объекта, с состоянием или без него, поэтому более полезен. Тем не менее, у этого есть затраты времени выполнения: чтобы скрыть обернутый тип, вызов его будет включать вызов виртуальной функции (или аналогичный); и может потребоваться динамическое выделение памяти для хранения большого типа.

Ответ 2

Я бы сказал (С++ 14)

template <class Functor, class... Args>
decltype(auto) apply_functor(Functor&& f, Args&&... args) {
    return std::forward<Functor>(f)(std::forward<Args>(args)...);
}

или С++ 11:

template <class Functor, class... Args>
auto apply_functor(Functor&& f, Args&&... args) ->decltype(std::forward<Functor>(f)(std::forward<Args>(args)...)) {
    return std::forward<Functor>(f)(std::forward<Args>(args)...);
}

Ответ 3

Если есть сомнения, предпочитайте std::function:

  • Синтаксис гораздо более прост и согласуется с другими типами, например,

    typedef std::function<int (int, int)> MyAddFunction;
    

    и

    typedef int (*MyAddFunction)( int, int );
    
  • Это более общий (на самом деле может принимать лямбда или простая функция C или функтор)

  • Это более безопасный тип

    apply_p(0, 0, 0); // probably crashes at runtime, dereferences null pointer
    apply_f(0, 0, 0); // doesn't even compile