2

How would I go about storing any type of function with any number of arguments in a variable? Something like this:

int add(int i, int j) {
return i + j;
}

template<typename Function, typename... Args>
class Class {
private:
    std::function<Function(Args...)> function;
public:
    Class(Function _function, Args... _args) {
        Now what?
    }
};

int main() {
    Class test(add, 1, 2);
    test.function();
}
user686368
  • 45
  • 1
  • 7

5 Answers5

5

You can try something like that

#include <iostream>
#include <functional>
#include <tuple>

int add(int i, int j) {
    return i + j;
}

template<typename Function, typename... Args>
class Class {
private:
    Function function_;
    std::tuple<Args...> args;
public:
    Class(Function _function, Args... _args) :  
        function_ { std::forward<Function>(_function) } ,
        args{std::forward<Args>(_args)...}
    {}

    auto function()
    {
        return std::apply(function_,args);
    }
};

int main() {
    Class test(add, 1, 2);
    std::cout<< test.function() << std::endl ;
}

Demo : https://wandbox.org/permlink/ZqFSSN2K5HU9HMRm

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
Martin Morterol
  • 2,560
  • 1
  • 10
  • 15
4

This is what bind is for!

Your Class is effectively just std::bind. Let's imagine that you have further use for it, and make it wrap the std::bind logic. So we need to store a std::function that accepts no further arguments, and returns (in this case) int… but we'll make it generic (ReturnType).

Then during initialisation you "bind" the arguments with which you constructed Class, and forward them into the std::function (the use of std::forward permitting move semantics for more complex argument types).

#include <functional>
#include <iostream>

int add(int i, int j)
{
    return i + j;
}

template <typename Function, typename... Args>
class Class
{
private:
    using ReturnType = std::invoke_result_t<Function, Args...>;
    std::function<ReturnType()> function;

public:
    Class(Function _function, Args... _args) 
        : function(std::bind(_function, std::forward<Args>(_args)...))
    {}

    auto Call()
    {
        return function();
    }
};

int main() {
    Class test(add, 1, 2);
    std::cout << test.Call() << '\n';
}

// Output: 3

(live demo)

Or, if you don't need Class to do anything more, it's just:

#include <functional>
#include <iostream>

int add(int i, int j)
{
    return i + j;
}

int main()
{
    auto func = std::bind(add, 1, 2);
    std::cout << func() << '\n';
}

// Output: 3

(live demo)


Bootnote

Your std::function<Function(Args...)> type is wrong because Function is the type of the whole function, but you've used it like a return type.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • 1
    -1: never use bind in generic code. If arguments are bind expressions/placeholders things go completely crazy in a way that nobody who hasn't implemented bind is likely to understand. Having an explosive corner case for no good reason is a bad idea in beginner, intermediate or advsnced C++ code; and avoiding it with bind is a real pain. – Yakk - Adam Nevraumont Jul 15 '19 at 11:22
  • @Yakk-AdamNevraumont I haven't implemented bind yet I've never had any trouble of the kind you refer to. Could you provide an example? – Lightness Races in Orbit Jul 15 '19 at 11:23
  • @LightnessRacesinOrbit What happens if you pass the result of `std::bind` to `std::bind`? "If the stored argument arg is of type T for which std::is_bind_expression::value == true (for example, another bind expression was passed directly into the initial call to bind), then bind performs function composition" -- https://en.cppreference.com/w/cpp/utility/functional/bind -- your example code above passes generic arguments to `std::bind`, which means that the placeholder/composition code kicks in, and that can break in ridiculous ways. – Yakk - Adam Nevraumont Jul 15 '19 at 13:51
3
template<class R>
struct Class {
  std::function<R()> function;
  template<class F, class...Args>
  Class( F&& f, Args&&...args ):
    function(
      [
        f=std::forward<F>(f),
        tup=std::make_tuple( std::forward<Args>(args)... )
      ]
      ()->R
      {
        return std::apply(f,tup);
      }
    )
  {} // ctor body
};
template<class F, class...Args>
Class( F, Args... )-> Class< std::invoke_result_t< F const&, Args const&... > >;

there.

int main() {
  Class test(add, 1, 2);
  test.function();
}

note that my Class type only depends on the return type.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
1

In c++11 and higher you can use Lambda closures.They do exactly what you describe.

For example:

int a = 1; 
int b = 2;
int c = [a, b]{ return a + b; }

Is equal to:

struct TestFunctor {
  int a;
  int b;
  TestFunctor(int a, int b):a(a), b(b){}
  int operator()(){return a+b;}
};

int a = 1; 
int b = 2;
int c = TestFunctor(a,b)();
printf("%d\n", c);

Lambda can be converted to std::function and called at any time... For example: https://wandbox.org/permlink/1RQUF3UUrsgRdJ42

  • This doesn't compile. You're storing the result, not the actual functor. And since the question is about how best to store things... (though a `template Class { CallableType func; };` with a stored `CallableType` that you can initialise from a lambda _is_ a good idea! Hit me up when you've made the corrections...) – Lightness Races in Orbit Jul 15 '19 at 11:24
  • In source example lambda also not stored to any place :) Here is what i mean: https://wandbox.org/permlink/SOVKItI2VNNA5FK6 – Arkadi Tolkun Jul 15 '19 at 11:28
  • Hm maybe i describe not good. Main idea is that if you need to call some function with some variable number of parameters - you can use function that initialized from lambda for that. – Arkadi Tolkun Jul 15 '19 at 11:32
1

The accepted answer is not good since std::bind can totally be replaced with lambda, using std::bind is old-fashioned in modern c++ and may cause some performance issue since std::function's construction is kind of heavy.

[ see Why use std::bind over lambdas in C++14? ]

Let me introduce c++20 std::bind_front, this function is intended to replace std::bind:

int add(int i, int j, int k) {
    return i + j + k;
}

auto f1 = std::bind_front(add, 1);
auto f2 = std::bind_front(add, 1, 2);
auto f3 = std::bind_front(add, 1, 2, 3);
// all will print '6'
std::cout << f1(2, 3) << '\n';
std::cout << f2(3) << '\n';
std::cout << f3() << '\n';

And don't forget to include the <functional> header :)

康桓瑋
  • 33,481
  • 5
  • 40
  • 90