Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I convert a std::function wrapper into a variadic function?

I have this very nice wrapper, but I'd like it to accept any number of T, S, R, Q, ...

template<typename U, typename T, typename S>
boost::variant<U, std::string> RunSafeFn2(const std::function<U(const T&,const S&)>& f, const std::string& errMsg, const  T& a1, const  S& a2)
{
    try
    {
        return f(a1,a2);
    }
    catch (...)
    {
        return errMsg;
    }
}

I tried the below and have been googling about, but man the error messages are cryptic - is what I'm trying to do even possible?

template<typename U, class ... Ts>
boost::variant<U, std::string> RunSafeFnN(const std::function<U(Ts)>& f, const std::string& errMsg, Ts ... ts)
{
    try
    {
        return bind(f, ts...);
    }
    catch (...)
    {
        return errMsg;
    }
}
like image 404
Carbon Avatar asked May 20 '26 22:05

Carbon


1 Answers

You can do what you want, as this simple program shows:

template <class ... Ts>
void foo(std::function<void(Ts...)> f, Ts && ... ts) {
    f(std::forward<Ts>(ts)...);
}

int main()
{
    std::function<void(int)> f = [] (int i) { std::cerr << "hello " << i; };
    foo(f, 5);
    return 0;
}

The thing is, that once RunSafeFn2 is a template, you may as well also template the functor itself. There's very little benefit to type erasing the callable when you are already a template. So in practice, it just makes more sense to do:

template <class F, class ... Ts>
void foo(F f, Ts && ... ts) {
    f(std::forward<Ts>(ts)...);
}

Which still allows the usage above, but also allows doing:

foo([] (int i) { std::cerr << "hello " << i; }, 5);

Which will also be more efficient since you completely avoid creating a std::function object. To handle the return type, since you're limited to C++11, you could do:

template <class F, class ... Ts>
auto foo(F f, Ts && ... ts) -> boost::variant<decltype(f(std::forward<Ts>(ts)...)), std::string> {
    try {
        return f(std::forward<Ts>(ts)...);
    }
    ...
}

Edit: let me add one final thought: In C++11 and on, where callables are so easy to create, a much simpler alternative to this is actually to take a callable and no arguments:

template <class F>
auto foo(F f) -> boost::variant<decltype(f()), std::string> {
    try {
        return f();
    }
    ...
}

That's because it's pretty easy to capture around the arguments that you need. For example, if I had written my original example that way, I could use it by doing:

int i = 5;
foo([&] () { std::cerr << "hello " << i; });

This has pros and cons especially perhaps when dealing with move only types, but if you want to minimize maintenance burden and your use cases are simple, this is a reasonable alternative.

like image 154
Nir Friedman Avatar answered May 23 '26 12:05

Nir Friedman