Передача указателя для умножения классов на функцию-член

У меня есть следующие занятия:

"Integrator.h"

#include <vector>
#include <array>
using namespace std;

class Integrator {
public:
    using coord_type = array<double, 3>;  
protected:
    void base_integrate_callback(const coord_type, double t_k) {
      //does nothing
    }
};

class MyIntegrator :public Integrator {
public:
   template <class T>
   void integrate(int mp_id, int t_span, int step ,
   void(T::*callback)(const coord_type, double) = (Integrator::*)(const coord_type, double)){
  //calls callback here
}
};

"main.cpp"

#include Integrator.h"

struct caller {
   void callback(const Integrator::coord_type coord, double t_k) {
   //does smth
}
};

int main(){
   MyIntegrator integrator_1;
   caller A;
   int mp_id = 1;
   int span = 365;
   int step = 1;
   integrator_1.integrate<caller>(mp_id,span,step,&A.callback);
   return 0;
}

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

файл:gration.h, строка 18, синтаксическая ошибка: '::*'

Как я могу вызвать обратный вызов, который может принадлежать любому классу?

И второй вопрос: когда я пытаюсь вызвать его без явной спецификации шаблона, как

integrator_1.integrate(mp_id,span,step,&A.callback);

Я получаю ошибку

file: main.cpp, строка 65, 'MyIntegrator::integrate': не найдена соответствующая перегруженная функция

Итак, почему эта функция не может вывести свой аргумент из своего параметра?

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

integrator_1.integrate(mp_id,span,step);

1 ответ

Решение

Расшифровка того, что у вас здесь, с небольшим отступом

template <class T>
void integrate(int mp_id, 
               int t_span, 
               int step ,
               void(T::*callback)(const coord_type, double) = (Integrator::*)(const coord_type, double))
{
    //calls callback here
}

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

template <class T>
void integrate(int mp_id, 
               int t_span, 
               int step ,
               void(T::*callback)(const coord_type, double) = &Integrator::base_integrate_callback)
{
    //calls callback here
}

но я не думаю, что это будет кошерно, так как нет способа гарантировать, что T а также Integrator в любом случае связаны.

Например, после уборки

integrator_1.integrate < caller > (mp_id, span, step, &A.callback);

в

integrator_1.integrate < caller > (mp_id, span, step, &caller::callback);

потому что вам нужно предоставить указатель на метод, а не объект, ссылающийся на метод. Это обнажает еще одну проблему, к которой мы скоро доберемся, но сейчас она скомпилируется и позволит нам продолжить.

Но это не

integrator_1.integrate < caller > (mp_id, span, step);

потому что подпись Integrator::base_integrate_callback, void Integrator::base_integrate_callback(constordin_type, double), не соответствует подписи void(caller::*callback)(const coord_type, double), Они выглядят одинаково, не так ли? Чего не хватает, так это скрытого this Параметр есть у всех методов. caller::*callback ожидает caller *, но Integrator::base_integrate_callback обеспечивает Integrator *,

Вы можете исправить это, сделав caller и это похоже на наследство Integrator скорее, чем MyIntegrator, но движется base_integrate_callback к новому struct Integrated и имея caller и друзья наследуют Integrated будет иметь больше смысла.

И вернемся к другой проблеме, о которой я упоминал ранее. В

template <class T>
void integrate(int mp_id, 
               int t_span, 
               int step ,
               void(T::*callback)(const coord_type, double) = &Integrated::base_integrate_callback)
{
    coord_type x; // junk for example
    double y; //junk for example
    callback(x,y); //KABOOM!
}

На какой объект вызывается обратный вызов? integrate потребуется еще один параметр, ссылка на T обеспечить контекст для callback,

template <class T>
void integrate(int mp_id, 
               int t_span, 
               int step,
               T & integrated,
               void(T::*callback)(const coord_type, double) = &Integrated::base_integrate_callback)
{
    coord_type x; // junk for example
    double y; //junk for example
    integrated.callback(x,y);
}

Затем вы должны использовать правильный синтаксис для вызова указателя функции, потому что выше всегда будет вызывать caller::callback,

template <class T>
void integrate(int mp_id, 
               int t_span, 
               int step,
               T & integrated,
               void(T::*callback)(const coord_type, double) = &Integrated::base_integrate_callback)
{
    coord_type x; // junk for example
    double y; //junk for example
    (integrated.*callback)(x,y); //std::invoke would be preferred if available
}

Все вместе:

#include <array>
#include <iostream>

class Integrator
{
public:
    using coord_type = std::array<double, 3>;
};

struct Integrated
{
    void base_integrate_callback(const Integrator::coord_type, double t_k)
    {
        std::cout << "made it to default" << std::endl;
    }
};

class MyIntegrator: public Integrator
{
public:
    template <class T>
    void integrate(int mp_id,
                   int t_span,
                   int step,
                   T & integrated,
            void(T::*callback)(const coord_type, double) = &Integrated::base_integrate_callback)
    {
        coord_type x; // junk for example
        double y = 0; //junk for example
        (integrated.*callback)(x,y);
    }
};


struct caller:public Integrated
{
    char val; // for test purposes
    caller(char inval): val(inval) // for test purposes
    {

    }
    void callback(const Integrator::coord_type coord, double t_k)
    {
        std::cout << "made it to " << val << std::endl;
    }
};

int main()
{
    MyIntegrator integrator_1;
    caller A {'A'};
    caller B {'B'};
    caller C {'C'};
    int mp_id = 1;
    int span = 365;
    int step = 1;
    integrator_1.integrate < caller > (mp_id, span, step, A, &caller::callback);
    integrator_1.integrate < caller > (mp_id, span, step, B, &caller::callback);
    integrator_1.integrate < caller > (mp_id, span, step, C);
    return 0;
}

Рекомендация: шагните в 2011 и посмотрите, что std::function и лямбда-выражения могут сделать для вас.

Вот пример:

#include <array>
#include <iostream>
#include <functional>

class Integrator
{
public:
    using coord_type = std::array<double, 3>;
};

// no need for integrated to get default callback

class MyIntegrator: public Integrator
{
public:
    template <class T>
    void integrate(int mp_id,
                   int t_span,
                   int step,
                   // no need to provide object instance for callback. packed with std::bind
                   std::function<void(const coord_type, double)> callback =
                           [](const coord_type, double) { std::cout << "made it to default" << std::endl; })
                           // default callback is now lambda expression
    {
        coord_type x; // junk for example
        double y = 0; //junk for example
        callback(x,y); // no weird syntax. Just call a function
    }
};


struct caller
{
    char val; // for test purposes
    // no need for test constructor
    void callback(const Integrator::coord_type coord, double t_k)
    {
        std::cout << "made it to " << val << std::endl;
    }
};

int main()
{
    MyIntegrator integrator_1;
    caller A {'A'};
    caller B {'B'};
    // no need for test object C
    int mp_id = 1;
    int span = 365;
    int step = 1;
    using namespace std::placeholders; // shorten placeholder names
    integrator_1.integrate < caller > (mp_id, 
                                       span, 
                                       step, 
                                       std::bind(&caller::callback, A, _1, _2));
    // std bind bundles the object and the callback together into one callable package

    integrator_1.integrate < caller > (mp_id, 
                                       span, 
                                       step, 
                                       [B](const Integrator::coord_type p1, 
                                           double p2) mutable // lambda captures default to const 
                                       { 
                                           B.callback(p1, p2); // and callback is not a const method
                                       });
    // Using lambda in place of std::bind. Bit bulkier, but often swifter and no 
    //need for placeholders

    integrator_1.integrate < caller > (mp_id,
                                       span,
                                       step,
                                       [](const Integrator::coord_type p1,
                                           double p2)
                                       {
                                           std::cout << "Raw Lambda. No callback object at all." << std::endl;
                                       });
    //custom callback without a callback object

    integrator_1.integrate < caller > (mp_id, span, step);
    //call default

    return 0;
}
Другие вопросы по тегам