Копирование функции лямбда с параметрами по умолчанию в переменную

Рассмотрим следующий код:

#include <iostream>
#include <functional>
using namespace std;

int main() {
    auto f = [](int a = 3) {cout << a << endl; };
    f(2); // OK
    f();  // OK

    auto g = f;
    g(2); // OK
    g();  // OK

    function<void(int)> h = f;
    h(2); // OK
    h();  // Error! How to make this work?

    return 0;
}

Как я могу объявить h вести себя так же, как f и g?

Ответ 1

std::function имеет одну фиксированную подпись. Это был выбор дизайна, а не жесткое требование. Написание псевдо std::function, поддерживающее несколько подписей, не сложно:

template<class...Sigs>
struct functions;

template<>
struct functions<> {
  functions()=default;
  functions(functions const&)=default;
  functions(functions&&)=default;
  functions& operator=(functions const&)=default;
  functions& operator=(functions&&)=default;
private:
  struct never_t {private:never_t(){};};
public:
  void operator()(never_t)const =delete;

  template<class F,
    std::enable_if_t<!std::is_same<std::decay_t<F>, functions>{}, int>* =nullptr
  >
  functions(F&&) {}
};

template<class S0, class...Sigs>
struct functions<S0, Sigs...>:
  std::function<S0>,
  functions<Sigs...>
{
  functions()=default;
  functions(functions const&)=default;
  functions(functions&&)=default;
  functions& operator=(functions const&)=default;
  functions& operator=(functions&&)=default;
  using std::function<S0>::operator();
  using functions<Sigs...>::operator();
  template<class F,
    std::enable_if_t<!std::is_same<std::decay_t<F>, functions>{}, int>* =nullptr
  >
  functions(F&& f):
    std::function<S0>(f),
    functions<Sigs...>(std::forward<F>(f))
  {}
};

использование:

auto f = [](int a = 3) {std::cout << a << std::endl; };

functions<void(int), void()> fs = f;
fs();
fs(3);

Живой пример.

Это создаст отдельную копию вашей лямбды для каждой перегрузки. Возможно даже иметь различные лямбды для разных перегрузок при тщательном литье.

Вы можете написать тот, который этого не делает, но в принципе потребовалось бы переопределить std::function внутреннее состояние fancier.

Более продвинутая версия вышеизложенного позволит избежать использования линейного наследования, поскольку это приводит к коду O (n ^ 2) и O (n) рекурсивной глубине шаблона в количестве подписей. Сбалансированное наследование двоичного дерева падает до 0 (n lg n) генерируемого кода и глубины O (lg n).

Версия промышленной прочности будет хранить переданную в лямбда один раз, использовать небольшую оптимизацию объектов, иметь ручную псевдо-таблицу, которая использовала двоичную стратегию наследования для отправки вызова функции и хранить указатели на отправленные функции в упомянутой псевдо-виртуальной таблице, Это займет O (# подписи) * sizeof (function pointer) пробел для каждого класса (не для каждого экземпляра) и будет использовать примерно столько же служебных данных для каждого экземпляра, сколько std::function.

Но это немного для SO-сообщения, нет?

начало

Ответ 2

Мне нравится красивое решение, предложенное @Yakk.

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

#include<functional>
#include<utility>
#include<iostream>

template<typename... Args>
void f(Args&&... args) {
    auto g = [](int a = 3) { std::cout << a << std::endl; };
    g(std::forward<Args>(args)...);
};

int main() {
    f(2);
    f();
}

Поместите вышеуказанные идеи в функтор, и у вас будет функционально подобный объект, который будет почти таким же.

У него есть проблема, что параметры строго не проверены. Во всяком случае, во время компиляции он будет неработоспособным.