Есть ли обобщенный c способ передачи указателей перегруженных методов, который требует меньше работы (чем в моем примере) - PullRequest
1 голос
/ 07 марта 2020

Итак, у меня есть функция, где, используя C ++ 17, я могу применить любой метод из любого объекта:

#include <functional>

template <typename Object, typename Method, typename ... Args>
void ApplyMethod (Object && object, Method && method, Args && ... args)
{
    std::invoke(method, object, args...);
}

Что я спрашиваю: есть ли способ улучшить это, требуя меньше работы для вызывающей функции при перегруженном методе.

Пример использования с перегруженными методами:

#include <iostream>

class Foo
{
    int bottles;

public:

    void Edit ()
    {
        bottles = 666;
    }

    void Edit (int number)
    {
        bottles = number;
    }

    void Talk () const
    {
        std::cout << bottles << " bottles of beer of the wall" << std::endl;
    }
};

class Bar
{
    Foo foo;

    void TrickEdit (int number)
    {
        // Because Foo::Edit is overloaded, we need to do some work:

        using Method = void (Foo::*)(int);

        Method ptr = &Foo::Edit;

        ApplyMethod(foo, ptr, number);
    }

    void TrickTalk () const
    {
        // it's a lot neater when the method isn't overloaded:

        ApplyMethod(foo, &Foo::Talk);
    }

public:

    void Trick ()
    {
        TrickEdit(900);
        TrickTalk();    
    }   
};


int main ()
{
    Bar().Trick();

    return 0;
}

Вместо этого я пытаюсь выполнить работу над функцией. Кажется, проблема l ie в том, что &Foo::Edit имеет два разных местоположения, в зависимости от того, на что мы ссылаемся Edit.

В C ++ FAQ - Указатели на функции-члены Страуструп и другие авторитетные авторы читают:

Вопрос: мне нужно что-то вроде указателей на функции, но с большей гибкостью и / или поточной безопасностью; Есть ли другой способ?

Ответ: Используйте функционоид.

Вопрос: Какого черта это функционоид, и зачем мне его использовать?

Ответ: Функциональные вещества являются функциями стероидов. Функциональные тела строго более мощные, чем функции, и эта дополнительная мощность решает некоторые (не все) проблемы, с которыми обычно сталкиваются при использовании указателей на функции. [...] Функциональные средства не решают всех проблем, возникающих при создании гибкого программного обеспечения, но они строго более мощные, чем указатели функций, и их стоит хотя бы оценить. Фактически вы можете легко доказать, что функционоиды не теряют никакой власти над указателями на функции, поскольку вы можете представить, что старомодный подход указателей на функции эквивалентен глобальному (!) Объекту-функции. Поскольку вы всегда можете создать глобальный функциональный объект, вы не потеряли ни одного основания. QED.

Учитывая, что «мощь» программирования в основном заключается в сокращении дублирования работы, и что с помощью обычной функции мы бы избежали дополнительной работы на сайте вызовов, которую я описал в моем проблема, ответ на часто задаваемые вопросы подразумевает, что должно быть решение этой проблемы с помощью функционоидов. Однако я не могу понять, как функционоиды в этом случае могут помочь.

Ответы [ 2 ]

2 голосов
/ 07 марта 2020

Может быть, вы можете использовать что-то подобное:

struct A
{   
    void Do() { std::cout << "Do no parm" << std::endl; }
    void Do(int) { std::cout << "Do 1 parm" << std::endl; }
    void Do(int,int) { std::cout << "Do 2 parms" << std::endl; }
};  

template < typename OBJ_TYPE, typename ... ARGS >
auto Invoke( OBJ_TYPE&& obj, void( std::remove_reference<OBJ_TYPE>::type::* func)(ARGS...), ARGS&& ... args )
{   
    return std::invoke( func, obj, args... );
}   

int main()
{   
    A a;
    Invoke( a, &A::Do);
    Invoke( a, &A::Do, 1); 
    Invoke( a, &A::Do,1,2);
}

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

Если у кого-то есть идея, как автоматически определять тип возвращаемого значения, чтобы мы также могли использовать перегрузки с разными типами возвращаемого значения, что было бы очень забавно! Я получаю рекурсию: -)

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

struct A
{   
    void Do() { std::cout << "Do no parm" << std::endl; }
    void Do(int) { std::cout << "Do 1 parm" << std::endl; }
    int Do(int,int) { std::cout << "Do 2 parms" << std::endl; return 42;}
};  


template < typename RETURN_TYPE, typename OBJ_TYPE, typename ... ARGS >
auto Invoke( OBJ_TYPE&& obj, RETURN_TYPE( std::remove_reference<OBJ_TYPE>::type::* func)(ARGS...), ARGS&& ... args )
{   
    return std::invoke( func, obj, args... );
}   

int main()
{   
    A a;
    Invoke<void>( a, &A::Do);
    Invoke<void>( a, &A::Do, 1); 
    int retval = Invoke<int>( a, &A::Do,1,2);
    std::cout << retval << std::endl;
}
1 голос
/ 11 марта 2020

Вы можете написать шаблон переменной, определяющий, каким должен быть Args....

template <typename... Args>
struct Overload {
    template<typename R, typename O>
    operator R(O::*)(Args...) (R(O::*p)(Args...)) const { return p; }
    template<typename R, typename O>
    operator R(O::*)(Args...) const (R(O::*p)(Args...) const) const { return p; }
};

template <typename... Args>
Overload overload;

Который используется как

struct A
{   
    void Do() { std::cout << "Do no parm" << std::endl; }
    void Do(int) { std::cout << "Do 1 parm" << std::endl; }
    void Do(int,int) { std::cout << "Do 2 parms" << std::endl; }
};  

template <typename Object, typename Method, typename ... Args>
void ApplyMethod (Object && object, Method && method, Args && ... args)
{
    std::invoke(method, object, args...);
}

int main()
{   
    A a;
    ApplyMethod( a, overload<>(&A::Do));
    ApplyMethod( a, overload<int>(&A::Do), 1); 
    ApplyMethod( a, overload<int, int>(&A::Do),1,2);
}

Это то, что Qt делает это современные сигналы и слоты.

...