3

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; } } 
2
  • 1
    From Clang, I get error: declaration type contains unexpanded parameter pack 'Ts' with squiggles under the Ts in function<U(Ts)>. If I were to judge, I'd say that's fairly clear. Commented Oct 5, 2017 at 21:11
  • Sure, but what's it telling me to do? Commented Oct 5, 2017 at 21:13

1 Answer 1

6

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.

Sign up to request clarification or add additional context in comments.

7 Comments

Well, bind is a total red herring. It's used to create one callable from another. But you don't want to create a callable, you just want to call it. std::forward isn't strictly necessary for calling a callable like this but it is the most correct way for various reasons.
Can you do anything nicer in C++14 for the return type? We're on MSVC 2015.2
Yeah your final thought is what I originally did, till people wanted the ability to write their own failure wrappers incorporating the arguments in the failure wrapper.
@ProbablyAStupidQuestion Well, see my update which simplifies things quite a bit in general. I'm actually not sure if there is an easy simplification in 14; I was thinking to just use auto but then I realized that's not very compatible with returning a variant.
It's a lot more elegant but they can do clever things if you pass the args, eg check return in light of args.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.