简体   繁体   中英

forwarding parameters indirectly deducible from function pointer type

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.

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 )...);
 }

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 );

Demo

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 .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM