6

For a library, I'd like a function to accept another function and its arguments, then store them all for calling later. The arguments must allow for any mixture of types, but the functions only need to return void. Something like this:

void myFunc1(int arg1, float arg2);
void myFunc2(const char *arg1);
class DelayedCaller
{ ...
public:
    static DelayedCaller *setup(Function func, …);
};

...
DelayedCaller* caller1 = DelayedCaller::setup(&myFunc1, 123, 45.6);
DelayedCaller* caller2 = DelayedCaller::setup(&myFunc2, "A string");

caller1->call(); // Calls myFunc1(), with arguments 123 and 45.6
caller2->call(); // Calls myFunc2(), with argument "A string"

One approach is to make DelayedCaller::setup() accept a std::function, and have my library users use std::bind() prior to calling setup(). However, is there a way to implement setup() such that users don't need to do the binding themselves?

Edit: DelayedCaller is an existing class. setup() is a new static method I'd like to add.

JKSH
  • 2,658
  • 15
  • 33
  • You could use std::async with lazy evaluation. It returns a std::future object that stores the task for later. – Felix Glas Feb 12 '13 at 13:01

4 Answers4

8

An possibility is to use variadic templates and call std::bind() from within the setup() function:

#include <iostream>
#include <string>
#include <functional>
#include <memory>

void myFunc1(int arg1, float arg2)
{
    std::cout << arg1 << ", " << arg2 << '\n';
}
void myFunc2(const char *arg1)
{
    std::cout << arg1 << '\n';
}

class DelayedCaller
{
public:
    template <typename TFunction, typename... TArgs>
    static std::unique_ptr<DelayedCaller> setup(TFunction&& a_func,
                                                TArgs&&... a_args)
    {
        return std::unique_ptr<DelayedCaller>(new DelayedCaller(
            std::bind(std::forward<TFunction>(a_func),
                      std::forward<TArgs>(a_args)...)));
    }
    void call() const { func_(); }

private:
    using func_type = std::function<void()>;
    DelayedCaller(func_type&& a_ft) : func_(std::forward<func_type>(a_ft)) {}
    func_type func_;
};

int main()
{
    auto caller1(DelayedCaller::setup(&myFunc1, 123, 45.6));
    auto caller2(DelayedCaller::setup(&myFunc2, "A string"));

    caller1->call();
    caller2->call();

    return 0;
}

Output:

123, 45.6
A string

Return a smart pointer, such as std::unique_ptr, instead of returning a raw pointer (or return by value and avoid dynamic allocation. The func_type is moveable if the arguments are moveable, or it may be quite cheap to copy anyway. You may need to define the move constructor and move assignment operator, they are generated under certain conditions).

hmjd
  • 120,187
  • 20
  • 207
  • 252
  • +1 for `unique_ptr` and templated function type. I forgot about that gotcha. – Fiktik Feb 12 '13 at 13:51
  • This is perfect; thanks. Extra thanks for the detailed integration of pointer safety and move semantics. – JKSH Feb 14 '13 at 15:13
7

You could use lambda functions to hide the bindings:

#include <functional>

class DelayedCaller : public std::function< void(void) > {
public:
    DelayedCaller(std::function< void(void) > fn)
      : std::function< void(void) >(fn) {}
};

DelayedCaller caller1([]() { myFunc1(123, 45.6); });
DelayedCaller caller2([]() { myFunc2("A string"); });

caller1(); // Calls myFunc1(), with arguments 123 and 45.6
caller2(); // Calls myFunc2(), with argument "A string"

This also gives your library's users more flexibility. They're not limited to a single function call and the functions have access to the original environment they were created in:

int x;

DelayedCaller caller3 = [&x]() {
    if (x == 0)
        DoSomething();
    else
        DoSomethingElse();
};
Ferruccio
  • 98,941
  • 38
  • 226
  • 299
  • This is certainly the route I would recommend over using `std::async`. – moswald Feb 12 '13 at 13:26
  • I didn't mention it in my question, but DelayedCaller needs to be a class as it's responsible for other things in my library. Still, thanks for thinking outside the box! – JKSH Feb 14 '13 at 15:19
  • @JKSH - Ok, I made it a class. – Ferruccio Feb 14 '13 at 17:31
  • @Ferruccio - Yes, that constructor would work. I've chosen hmjd's answer for my problem though, as the resulting API matches my original code. I appreciate your efforts. – JKSH Feb 17 '13 at 03:06
0

If you want/are able to make use of the C++11 future library you can use std::async

#include <future>

auto caller = std::async(myFunc1, 123, 45.6); // Creates a future object.

caller.get(); // Waits for the function to get executed and returns result.

To force lazy evaluation use:

auto caller = std::async(std::launch::deferred, myFunc1, 123, 45.6);

Also this has the advantage that the function call might be executed on a different thread which makes use of multicore hardware. However this may not be suitable in every case.

Felix Glas
  • 15,065
  • 7
  • 53
  • 82
  • This isn't suitable for my case, as I'm extending a custom thread-management API :) Still, thanks for demonstrating the use of std::async(), which was new to me – JKSH Feb 17 '13 at 02:23
0

If your only concern is to hide the argument binding from the callsite while keeping your interface, use variadic templates

class DelayedCaller
{
public:
  template<typename... Args>
  static DelayedCaller* setup(void (functionPtr*)(Args...), Args&&... args)
  {
    return new DelayedCaller(std::bind(functionPtr, std::forward<Args>(args)...));
  }

  DelayedCaller(const std::function<void()>& f) : f(f) {}

private:
  std::function<void()> f;
};

The public constructor still offers your users the possibility to initialize it with a lambda is they wish.

Fiktik
  • 1,941
  • 16
  • 25
  • Yes, the simple interface was my goal. I picked hmjd's answer for its extra robustness, but this minimalistic example is great for beginners like myself; thanks for sharing. – JKSH Feb 14 '13 at 15:17