параметры пересылки косвенно выводятся из типа указателя функции - PullRequest
0 голосов
/ 23 января 2019

Я хочу иметь функцию, которая принимает указатель на функцию и пересылает все параметры, как указано самим типом указателя на функцию, например:

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
}

Функции для вызова для обоих примеров:

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

Сбой в случае Ex2 и Ex3 просто при попытке два раза определить тип списка ARGS, и результат будет другим. Компилятор жалуется на:

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'

После этого я попытался обойти проблему следующим подходом, так как я выбираю типы только один раз, выводя список ARGS, и снова пересылаю к промежуточной лямбде следующим образом:

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

В: Есть ли способ исправить первый подход в любом случае, чтобы избавиться от промежуточной лямбды здесь? А если нет, хорошо ли спроектирована промежуточная лямбда-решение, особенно со всеми «переадресационными» вещами, чтобы я не создавал копий или других неожиданных действий?

EDIT: Это только сокращенный пример. Я не намерен писать копию std::invoke. Так что в моем реальном мире кода гораздо больше можно сделать внутри самого метода Do.

Важно получить необходимые типы из типа указателя функции, так как я должен выполнить некоторые проверки внутри Do, которые относятся к типам, предоставляемым указателем функции, а не из данного дополнительного параметры, которые я предоставляю из кода пользователя для метода Do.

Ответы [ 2 ]

0 голосов
/ 23 января 2019

В: Есть ли способ исправить первый подход в любом случае, чтобы избавиться от промежуточной лямбды здесь?

Вы можете изменить вторые аргументы, чтобы они не выводились:

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

Демонстрация

- это решение с промежуточной лямбда, "хорошо" разработанной, особенно со всеми вещами "пересылки", так что я не создал некоторыекопии или другое неожиданное поведение?

Вы делаете дополнительные конструкции перемещения, поэтому для void Ex1(std::array<int, 5>) вы копируете вдвое больше std::array.Решение пересылает ссылку:

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

Простые альтернативы:

template < typename Ret, typename ... Ts, typename ... Args >
auto Do1( Ret(*ptr)(Ts...), Args&& ... args)
{
    (*ptr)(std::forward<Args>(args)...);
}

или даже

template < typename Func, typename ... Args >
auto Do1(Func f, Args&& ... args)
{
    f(std::forward<Args>(args)...);
}

Возможно, у вас еще есть function_traits для проверки Func.

0 голосов
/ 23 января 2019

В: Есть ли способ исправить первый подход в любом случае, чтобы избавиться от промежуточной лямбды здесь?

Я предлагаю принять вызываемый ptr просто как тип

template < typename F, typename ... ARGS >
auto Do1( F func, ARGS && ... args )
 {
    func(std::forward<ARGS>( args )...);
 }

Таким образом, Do1() полностью исключает двойную проблему дедукции и работает также с другими типами вызываемых объектов (например, с общей лямбдой, которую нельзя просто преобразовать в указатель на функцию).

В противном случае вы можете перехватить два переменных типа списка аргументов

template < typename RET, typename ... AS1, typename ... AS2 >
auto Do1( RET(*ptr)(AS1...), AS2 && ... args )
 {
   (*ptr)(std::forward<AS2>( args )...);
 }
...