c++templatesmember-pointers

passing pointer to multiply classes to a member function


I have the next classes:

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

Trying to compile it I get an error:

file:integration.h, line 18, syntax error: '< tag>::*'

How can I call a callback which could belong to any class?

And the second question: when I try to call it without explicit template specification like

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

I get an error

file: main.cpp , line 65, 'MyIntegrator::integrate': no matching overloaded function found

So, why this function can not deduce its argument from its parameter?

Also I get the same error when calling it without the last parameter relying on the default parameter.

integrator_1.integrate(mp_id,span,step);

Solution

  • Decrypting what you have here with a little indentation

    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
    }
    

    it looks like you are trying to declaring a method that takes a callback function as a parameter and assigning a default value. Unfortunately the default value looks like the declaration of another method pointer and not a method. You need to use a pointer to a method of 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
    }
    

    but I don't think this will be kosher as there is no way to ensure that T and Integrator are in any way related.

    For example, after cleaning up

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

    to

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

    because you need to provide a pointer to a method, not an object referring to a method. This exposes another problem we'll get to in a moment, but it will compile for now and let us continue.

    But this would not

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

    because the signature of Integrator::base_integrate_callback, void Integrator::base_integrate_callback(const coord_type, double), does not match the signature of void(caller::*callback)(const coord_type, double). They look the same, don't they? What's missing is the hidden this parameter all methods have. caller::*callbackexpects a caller *, but Integrator::base_integrate_callback provides Integrator *.

    You can fix this by making caller and it's ilk inherit Integrator rather than MyIntegrator, but moving base_integrate_callback to a new struct Integrated and having caller and friends inherit Integrated would make more sense.

    And back to the other problem I mentioned earlier. In

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

    On what object is callback being invoked? integrate will need one more parameter, a reference to T to provide context for 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);
    }
    

    Then you have to use the correct syntax to invoke the function pointer because the above will always call 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
    }
    

    All together:

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

    Recommendation: Step into 2011 and see what std::function and lambda expressions can do for for you.

    Here's an example:

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