I'm writing a template wrapper function that can be applied to a functions with different number/types of arguments. I have some code that works but I'm trying to change more arguments into template parameters.
The working code:
#include <iostream>
int func0(bool b) { return b ? 1 : 2; }
//There is a few more funcX...
template<typename ...ARGS>
int wrapper(int (*func)(ARGS...), ARGS... args) { return (*func)(args...) * 10; }
int wrappedFunc0(bool b) { return wrapper<bool>(func0, b); }
int main()
{
std::cout << wrappedFunc0(true) << std::endl;
return 0;
}
Now I want int (*func)(ARGS...)
to also be a template parameter. (It's for performance reasons. I want the pointer to be backed into the wrapper, because the way I'm using it prevents the compiler from optimizing it out.)
Here is what I came up with (The only difference is I've changed the one argument into a template parameter.):
#include <iostream>
int func0(bool b) { return b ? 1 : 2; }
//There is a few more funcX...
template<typename ...ARGS, int (*FUNC)(ARGS...)>
int wrapper(ARGS... args) { return (*FUNC)(args...) * 10; }
int wrappedFunc0(bool b) { return wrapper<bool, func0>(b); }
int main()
{
std::cout << wrappedFunc0(true) << std::endl;
return 0;
}
This doesn't compile. It shows:
<source>: In function 'int wrappedFunc0(bool)':
<source>:9:55: error: no matching function for call to 'wrapper<bool, func0>(bool&)'
9 | int wrappedFunc0(bool b) { return wrapper<bool, func0>(b); }
| ~~~~~~~~~~~~~~~~~~~~^~~
<source>:7:5: note: candidate: 'template<class ... ARGS, int (* FUNC)(ARGS ...)> int wrapper(ARGS ...)'
7 | int wrapper(ARGS... args) { return (*FUNC)(args...) * 10; }
| ^~~~~~~
<source>:7:5: note: template argument deduction/substitution failed:
<source>:9:55: error: type/value mismatch at argument 1 in template parameter list for 'template<class ... ARGS, int (* FUNC)(ARGS ...)> int wrapper(ARGS ...)'
9 | int wrappedFunc0(bool b) { return wrapper<bool, func0>(b); }
| ~~~~~~~~~~~~~~~~~~~~^~~
<source>:9:55: note: expected a type, got 'func0'
ASM generation compiler returned: 1
<source>: In function 'int wrappedFunc0(bool)':
<source>:9:55: error: no matching function for call to 'wrapper<bool, func0>(bool&)'
9 | int wrappedFunc0(bool b) { return wrapper<bool, func0>(b); }
| ~~~~~~~~~~~~~~~~~~~~^~~
<source>:7:5: note: candidate: 'template<class ... ARGS, int (* FUNC)(ARGS ...)> int wrapper(ARGS ...)'
7 | int wrapper(ARGS... args) { return (*FUNC)(args...) * 10; }
| ^~~~~~~
<source>:7:5: note: template argument deduction/substitution failed:
<source>:9:55: error: type/value mismatch at argument 1 in template parameter list for 'template<class ... ARGS, int (* FUNC)(ARGS ...)> int wrapper(ARGS ...)'
9 | int wrappedFunc0(bool b) { return wrapper<bool, func0>(b); }
| ~~~~~~~~~~~~~~~~~~~~^~~
<source>:9:55: note: expected a type, got 'func0'
Execution build compiler returned: 1
(link to the compiler explorer)
It looks like a problem with the compiler to me, but GCC and Clang agree on it so maybe it isn't.
Anyway, how can I make this template compile correctly with templated pointer to a function?
EDIT: Addressing the duplicate flag Compilation issue with instantiating function template I think the core of the problem in that question is the same as in mine, however, it lacks a solution that allows passing the pointer to function (not only its type) as a template parameter.
CodePudding user response:
This doesn't work because a pack parameter (the one including ...
) consumes all remaining arguments. All arguments following it can't be specified explicitly and must be deduced.
Normally you write such wrappers like this:
template <typename F, typename ...P>
int wrapper(F &&func, P &&... params)
{
return std::forward<F>(func)(std::forward<P>(params)...) * 10;
}
(And if the function is called more than once inside of the wrapper, all calls except the last can't use std::forward
.)
This will pass the function by reference, which should be exactly the same as using a function pointer, but I have no reasons to believe that it would stop the compiler from optimizing it.
You can force the function to be encoded in the template argument by passing std::integral_constant<decltype(&func0), func0>{}
instead of func0
, but again, I don't think it's going to change anything.
CodePudding user response:
The 2nd snippet is not valid because:
a type parameter pack cannot be expanded in its own parameter clause.
As from [temp.param]/17:
If a template-parameter is a type-parameter with an ellipsis prior to its optional identifier or is a parameter-declaration that declares a pack ([dcl.fct]), then the template-parameter is a template parameter pack. A template parameter pack that is a parameter-declaration whose type contains one or more unexpanded packs is a pack expansion. ... A template parameter pack that is a pack expansion shall not expand a template parameter pack declared in the same template-parameter-list.
So consider the following invalid example:
template<typename... Ts, Ts... vals> struct mytuple {}; //invalid
The above example is invalid because the template type parameter pack Ts
cannot be expanded in its own parameter list.
For the same reason, your code example is invalid. For example, a simplified version of your 2nd snippet doesn't compile in msvc.