Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Copying a lambda function with default parameters to a variable

Tags:

c++

c++11

lambda

Consider the following code:

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

How can I declare h to behave the same as f and g?

like image 237
Eissa N. Avatar asked Jun 09 '16 00:06

Eissa N.


People also ask

Can a lambda function be assigned to a variable?

Creating a Lambda Function The lambda operator cannot have any statements and it returns a function object that we can assign to any variable.

Can Python lambda have default arguments?

Defaults in Python Lambda ExpressionIn Python, and in other languages like C++, we can specify default arguments.

How do you pass parameters in lambda function in Python?

A Python lambda function behaves like a normal function in regard to arguments. Therefore, a lambda parameter can be initialized with a default value: the parameter n takes the outer n as a default value. The Python lambda function could have been written as lambda x=n: print(x) and have the same result.

Can lambda functions take parameters?

A lambda function can have any number of parameters, but the function body can only contain one expression.


2 Answers

std::function has one fixed signature. This was a design choice, not a hard requirement. Writing a pseudo std::function that supports multiple signatures isn't hard:

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

use:

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

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

Live example.

This will create a separate copy of your lambda per overload. It is even possible to have different lambdas for different overloads with careful casting.

You can write one that doesn't do this, but it would basically require reimplementing std::function with a fancier internal state.

A more advanced version of the above would avoid using linear inheritance, as that results in O(n^2) code and O(n) recursive template depth on the number of signatures. A balanced binary tree inheritance drops that down to O(n lg n) code generated and O(lg n) depth.

The industrial strength version would store the passed in lambda once, use small object optimization, have a manual pseudo-vtable that used a binary inheritance strategy to dispatch the function call, and store dispatched-to function pointers in said pseudo-vtable. It would take O(# signatures)*sizeof(function pointer) space in a per-class (not per-instance) basis, and use about as much per-instance overhead as std::function would.

But that is a bit much for a SO post, no?

start of it

like image 91
Yakk - Adam Nevraumont Avatar answered Oct 13 '22 09:10

Yakk - Adam Nevraumont


I like the nice solution proposed by @Yakk.

That said, you can do something similar by avoiding the std::functions and using a proxy function for the lambda, variadic templates and std::forward, as it follows:

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

Put the above ideas in a functor and you'll have a function-like object that does almost the same.

It has the problem that parameters are not strictly verified.
Anyway, it will fail at compile time if misused.

like image 42
skypjack Avatar answered Oct 13 '22 09:10

skypjack