2

Assume there is a template function foo() which accepts an arbitrary number of arguments. Given the last argument is always an std::function, how do I implement a foo() template shown below in a way that CbArgs would contain this std::function's parameters?

template<typename... InArgs, typename... CbArgs = ???>
//                                       ^^^^^^^^^^^^
void foo(InArgs... args) { ... }

For example, CbArgs should be {int,int} if invoked like this:

std::function<void(int,int)> cb;
foo(5, "hello", cb);

My first idea was:

template<typename... InArgs, typename... CbArgs>
void foo(InArgs... args, std::function<void(CbArgs...)>) { ... }

But this does not compile:

note:   template argument deduction/substitution failed:
note:   mismatched types ‘std::function<void(CbArgs ...)>’ and ‘int’
  foo(5, "hello", cb);

Question One:
Why doesn't this compile? Why does the template argument deduction fail?


Eventually, I came up this solution:

template<typename... InArgs, typename... CbArgs>
void fooImpl(std::function<void(CbArgs...)>, InArgs... args) { ... }

template<typename... InArgs,
         typename CbType = typename std::tuple_element_t<sizeof...(InArgs)-1, std::tuple<InArgs...>>>
void foo(InArgs... args)
{
    fooImpl(CbType{}, args...);
}

Here CbType is the last type in InArgs which is std::function. Then a temporary of CbType is passed to fooImpl() where CbArgs are deduced. This works, but looks ugly to me.

Question Two:
I wonder if there is a better solution without having two functions and a temporary instance of CbType?

Kane
  • 5,595
  • 1
  • 18
  • 27
  • but is necessary that the function is in last position? Or can be in first position? – max66 Feb 19 '17 at 22:47
  • It has to be in the last position. This is needed for wrapping asynchronous functions, which have a callback as their last argument. – Kane Feb 19 '17 at 23:05
  • "have to" -- I'm sorry, I can write an asynchronous function that doesn't have a callback as the last argument. So in what way does it "have to" have a callback as its last argument? Is it just that you prefer continuation passing style? You think having "what happens next" be after "what you are doing"? There are lots of reasons, but "asynchronous functions must have a callback as their last argument" skips over the reason and just states your conclusion. – Yakk - Adam Nevraumont Feb 20 '17 at 00:08
  • Have you looked at [n3865](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3865.pdf) and considered that approach instead? – Yakk - Adam Nevraumont Feb 20 '17 at 00:43
  • Okay, let me rephrase. Technically, a callback does not have to be the last argument. But _usually_ it is the last argument. If you think I am wrong, please give me real life examples of the opposite. Anyway, all I want is to write a wrapper, which would convert lots of _already written_ functions with a callback in the last position to functions which would return `future` instead. E.g. `convertToFuture(&foo, 5, "hello")` would call `foo(5, "hello", )` and return `future>`, which is fulfilled when the callback is invoked. – Kane Feb 20 '17 at 09:55

1 Answers1

3

Why doesn't this compile? Why does the template argument deduction fail?

When a parameter pack is not the last parameter, it cannot be deduced. Telling the compiler the contents of InArgs... will make your foo definition work:

template<typename... InArgs, typename... CbArgs>
void foo(InArgs..., std::function<void(CbArgs...)>) { }

int main()
{
    std::function<void(int,int)> cb;
    foo<int, const char*>(5, "hello", cb);
}

Alternatively, as you discovered in your workaround, simply put InArgs... at the end and update your foo invocation:

template<typename... InArgs, typename... CbArgs>
void foo(std::function<void(CbArgs...)>, InArgs...) { }

int main()
{
    std::function<void(int,int)> cb;
    foo(cb, 5, "hello");
}

I wonder if there is a better solution without having two functions and a temporary instance of CbType?

Here's a possible way of avoiding the unnecessary temporary instance but using your same mechanism for the deduction of CbArgs...: simply wrap CbType in an empty wrapper, and pass that to fooImpl instead.

template <typename T>
struct type_wrapper
{
    using type = T;
};

template<typename... InArgs, typename... CbArgs>
void fooImpl(type_wrapper<std::function<void(CbArgs...)>>, InArgs&&...) { }

template<typename... InArgs,
         typename CbType = 
             std::tuple_element_t<sizeof...(InArgs)-1, 
                 std::tuple<std::remove_reference_t<InArgs>...>>>
void foo(InArgs&&... args)
{
    fooImpl(type_wrapper<CbType>{}, std::forward<InArgs>(args)...);
}

Additional improvements:

  • The typename after typename CbType = was unnecessary - it was removed.

  • args... should be perfectly-forwarded to fooImpl to retain its value category. Both foo and fooImpl should take args... as a forwarding-reference.

wandbox example


Note that there is a proposal that would make dealing with non-terminal parameter packs way easier: P0478R0 - "Template argument deduction for non-terminal function parameter packs". That would make your original implementation work as intended.

Vittorio Romeo
  • 90,666
  • 33
  • 258
  • 416
  • 1
    So not worth it. +1. I would recommend either putting the `std::function` first, *or* breaking the function call down into multiple calls. – Yakk - Adam Nevraumont Feb 20 '17 at 00:06
  • @Yakk `std::function` has to be the last. Not because "asynchronous functions must have a callback as their last argument" (I have never stated that), but because it is a condition for this specific task. – Kane Feb 20 '17 at 10:06
  • Vittorio, thank you for the hint regarding `type_wrapper`. I think I will do it like this. – Kane Feb 20 '17 at 10:14