Search code examples
c++lambdac++14std-function

how to make std::functions and lambdas which could take any type of argument


I have simplified this as much as I can and hopefully, it will be clear. I have a class called Foo which contains an std::function member that is bind to a function that I want to call.

When Foo::DoCallback is called, the function that is a bind or assigned is executed. In order to support the different types of parameters, I have created 2 constructors and a default constructor that takes no parameter

a constructor that takes std::function with an int type parameter.
Foo(std::function<void(int)> callback, int a)

a constructor that takes std::function that takes a bool parameter.

Foo(std::function<void(bool)> callback, bool condition)

and the default constructor that takes no parameters at all.

Foo(std::function<void()> callback = nullptr) : Callback(callback)

in the main, I have 3 lambdas. each lambda takes a different type of parameters and passes them as an argument to the function that would be used as a callback.

  auto lambdaA = [](int a)
  {
    FunctionA(a);
  };

  auto lambdaB = [](bool condition)
  {
    FunctionB(condition);
  };

  auto lambdaC = []()
  {
    FunctionC();
  };

The problem is that, whenever I need a new callback with different types of argument, I have to keep creating;

  • a lambda that needs to be passed as an argument when creating the Foo unique ptr.
  • a foo constructor that takes the new type of std::function parameter that takes the additional types of parameter.


Is there a way to make this simpler?

here is the full code

--main--

#include <iostream>
#include <string>
#include <memory>
#include "Foo.h"
void FunctionA(int a)
{
  std::cout << "FunctionA called. param 1 = " << std::to_string(a) << "\n";
}

void FunctionB(bool condition)
{
  std::cout << "FunctionB called. param 1 = " << std::to_string(condition) << "\n";
}

void FunctionC()
{
  std::cout << "FunctionC called with no params" << "\n";
}
int main()
{
  auto lambdaA = [](int a)
  {
    FunctionA(a);
  };

  auto lambdaB = [](bool condition)
  {
    FunctionB(condition);
  };

  auto lambdaC = []()
  {
    FunctionC();
  };

  std::unique_ptr<Foo> FooPtrA = std::make_unique<Foo>(lambdaA,10);
  std::unique_ptr<Foo> FooPtrB = std::make_unique<Foo>(lambdaB ,false);
  std::unique_ptr<Foo> FooPtrC = std::make_unique<Foo>(lambdaC);

  FooPtrA->DoCallback();
  FooPtrB->DoCallback();
  FooPtrC->DoCallback();

  return 0;
}

-- Foo.h

#pragma once
#include <functional>
class Foo
{
public:
  //constructor with a parameter callback which could take an int parameter
  Foo(std::function<void(int)> callback, int a) 
  {
    Callback = std::bind(callback, a);
  }
  //constructor with a parameter callback which could take a bool parameter
  Foo(std::function<void(bool)> callback, bool condition)
  {
    Callback = std::bind(callback, condition);
  }

  //constructor with a parameter callback which takes no parameter
  Foo(std::function<void()> callback = nullptr) : Callback(callback)
  {
  }

  void DoCallback()
  {
    Callback(); //calling the callback function that is binded
  }
private:
  std::function<void()> Callback;
};

Solution

  • Going one stage further than Swordfish's answer, you can eliminate the overhead of an std::function by parametrizing with any type:

    template<typename F, typename T>
    Foo(F f, T a)
        : Callback{ std::bind(f, a) } } {}
    

    If you want this to work for arbitrary numbers of arguments, and call move constructors correctly:

    template<typename F, typename... Args>
    Foo(F&& f, Args&&... a)
        : Callback{ std::bind(std::forward<F>(f), std::forward<Args>(a)...) } {}