Template argument and deduction of std::function parameters

廉价感情. 提交于 2020-01-24 12:07:38

问题


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?


回答1:


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.



来源:https://stackoverflow.com/questions/42333734/template-argument-and-deduction-of-stdfunction-parameters

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!