Reputation: 25593
I want to have a function which accepts a pointer to a function and forwards all parameters as given by the function pointer type itself like this:
template < typename RET, typename ... ARGS >
auto Do1( RET(*ptr)(ARGS...), ARGS... args )
{
(*ptr)(std::forward<ARGS>( args )...);
}
int main ()
{
int i=4;
Do1( &Ex1, i );
Do1( &Ex2, i ); //fails!
Do1( &Ex3, i+1 ); // fails
}
Functions to call are for both examples:
void Ex1( int i){ std::cout << __PRETTY_FUNCTION__ << " " << i << std::endl; i=10;}
void Ex2( int& i){ std::cout << __PRETTY_FUNCTION__ << " " << i << std::endl; i=20;}
void Ex3( int&& i){ std::cout << __PRETTY_FUNCTION__ << " " << i << std::endl; i=30;}
It fails in case of Ex2
and Ex3
simply while it tries to deduce the type of the ARGS list two times and the result is different. The compiler complains with:
main.cpp:57:22: error: no matching function for call to 'Do1(void (*)(int&), int&)'
Do1( &Ex2, i ); //fails!
^
main.cpp:33:10: note: candidate: 'template<class RET, class ... ARGS> auto Do1(RET (*)(ARGS ...), ARGS ...)'
auto Do1( RET(*ptr)(ARGS...), ARGS... args )
^~~
main.cpp:33:10: note: template argument deduction/substitution failed:
main.cpp:57:22: note: inconsistent parameter pack deduction with 'int&' and 'int'
After that I tried to work around the problem with the following approach as I pick up the types by only one time deducing the ARGS list and forward once more to a intermediate lambda as follows:
template < typename RET, typename ... ARGS >
auto Do2( RET(*ptr)(ARGS...) )
{
return [ptr]( ARGS ... args )
{
std::cout << __PRETTY_FUNCTION__ << std::endl;
(*ptr)(std::forward<ARGS>(args)...);
};
}
int main ()
{
int i=4;
Do1( &Ex1, i );
Do1( &Ex2, i ); //fails!
Do1( &Ex3, i+1 ); // fails
Do2( &Ex1 )( i );
std::cout << "now i: " << i << std::endl;
std::cout << std::endl;
Do2( &Ex2 )( i );
std::cout << "now i: " << i << std::endl;
std::cout << std::endl;
Do2( &Ex3 )( i+1 );
std::cout << "now i: " << i << std::endl;
std::cout << std::endl;
}
Q: Is there any way to repair the first approach in any case to get rid of the intermediate lambda here? And if not, is the solution with the intermediate lambda "well" designed, especially with all the "forwarding" things so that I did not create some copies or other unexpected behavior?
EDIT:
This is only a reduced example. It is not my intend to write a copy of std::invoke
. So there is in my real world code a lot more to do inside the Do
method itself.
It is important to get the needed types from the function pointer type as I have to perform some checks inside Do
which are related to the types provided by the function pointer and not from the given additional parameters I provide from the user code to the Do
method.
Upvotes: 1
Views: 171
Reputation: 217085
Q: Is there any way to repair the first approach in any case to get rid of the intermediate lambda here?
You might change second args to be non deducible:
template <typename T>
struct non_deducible {
using type = T;
};
template <typename T>
using non_deducible_t = typename non_deducible<T>::type;
template < typename RET, typename ... ARGS >
auto Do1( RET(*ptr)(ARGS...), non_deducible_t<ARGS>... args );
is the solution with the intermediate lambda "well" designed, especially with all the "forwarding" things so that I did not create some copies or other unexpected behavior?
You do extra move constructs, so for void Ex1(std::array<int, 5>)
, you copy twice the std::array
.
Solution is forwarding reference:
template < typename RET, typename ... ARGS >
auto Do2( RET(*ptr)(ARGS...) )
{
return [ptr](auto&& ... args )
-> decltype((*ptr)((decltype(args)(args))...))
{
std::cout << __PRETTY_FUNCTION__ << std::endl;
(*ptr)((decltype(args)(args))...);
};
}
Simple alternatives are:
template < typename Ret, typename ... Ts, typename ... Args >
auto Do1( Ret(*ptr)(Ts...), Args&& ... args)
{
(*ptr)(std::forward<Args>(args)...);
}
or even
template < typename Func, typename ... Args >
auto Do1(Func f, Args&& ... args)
{
f(std::forward<Args>(args)...);
}
You might still have some function_traits
to check Func
.
Upvotes: 3
Reputation: 66200
Q: Is there any way to repair the first approach in any case to get rid of the intermediate lambda here?
I suggest to accept the callable ptr
simply as a type
template < typename F, typename ... ARGS >
auto Do1( F func, ARGS && ... args )
{
func(std::forward<ARGS>( args )...);
}
This way your Do1()
avoid completely the double-different deduction problem and works also with other types of callable (by example: with generic lambda that can't be simply converted to function pointer).
Otherwise you can intercept two variadic list of argument types
template < typename RET, typename ... AS1, typename ... AS2 >
auto Do1( RET(*ptr)(AS1...), AS2 && ... args )
{
(*ptr)(std::forward<AS2>( args )...);
}
Upvotes: 3