Florian Pasquereau
Florian Pasquereau

Reputation: 132

How Call child method from parent class

I did a small exemple to try to explain you with my poor english what I want to do :).

I have a main class who is my engine. This is my parent class of several children.

this is the parent class :

#include <string>
#include <iostream>
#include <vector>

template <typename Type>
class A
{
    public:
        A(std::string const &str)
        : m_str(str)
        {
        }

        void run(void) const {
            unsigned int    i;

            for(i = 0; ACTIONS[i].f != nullptr; i++) {
                if(m_str == ACTIONS[i].key) {
                    return ((*(this).*ACTIONS[i].f)(m_str));
                }
            }
        }
    protected:
        typedef struct s_action {
            std::string key;
            void (Type::*f)(std::string const &);
        } t_action;
        static t_action const ACTIONS[];
        std::string m_str;
};


class B : public A<B>
{
    public:
        B(std::string const &str);
    protected:
        static t_action const ACTIONS[];
        void error(std::string const &str);
        void success(std::string const &str);
};

I would like to call children method with table pointer of member function in this parent class A::run as you can see above

This code does not compile.

I know it's not possible to have a static variable virtual, but it's exactly that I need to do have for A::ACTIONS. I absolutely need to initialise B::ACTIONS to A::run works.

In first Is it possible? Have you got a small exemple of this case?

This is the end of my small code :

#include "Class.hpp"

B::t_action const B::ACTIONS[] = {
    {"ERROR", &B::error},
    {"SUCCESS", &B::success},
    {"", nullptr}
};

B::B(std::string const &str)
: A<B>(str)
{
}

void B::error(std::string const &str) {
    std::cerr << str << std::endl;
}

void B::success(std::string const &str) {
    std::cout << str <<std::endl;
}

And the main:

#include "Class.hpp"

int main() {
    B b("SUCCESS");
    b.run();
    return (0);
}

I didn't try, normally this code should Display SUCCESS on stdout

Thank you for your help

Upvotes: 2

Views: 1325

Answers (3)

Red.Wave
Red.Wave

Reputation: 4201

If you are going to use CRTP, IMO you need to google for CRTP first. By the way here's a quick direct ans 2 your q:

template<typename crtp_child>
class crtp_base{
    using crtp_target=crtp_child;
    auto crtp_this(){
        return static_cast<crtp_target*>(this);
    };

    auto crtp_this() const {
        return static_cast<crtp_target const*>(this);
    };
public:
    void run(){
        auto range=crtp_this()->actions.equal_range(m_str);
        for(auto entry:range)
            (crtp_this()->*(entry.second))(m_str);
    };
protected:
    crtp_base(std::string str):
        m_str(str)
    {};
    std::string m_str;
    //...
};

struct crtp_user:
    crtp_base<crtp_user>
{
    using crtp_base::crtp_base;//ctor fwding
protected:
    friend class crtp_base<crtp_user>;
    std::unordered_multimap<std::string, void (crtp_user::*)(std::string)> actions;
    //...
};

Upvotes: 0

Sam Varshavchik
Sam Varshavchik

Reputation: 118310

    void run(void) const
    {
        unsigned int    i;

        for(i = 0; ACTIONS[i].f != nullptr; i++)
            if (m_str == ACTIONS[i].key)
                return ((*(this).*ACTIONS[i].f)(m_str));
    }

There are multiple reasons why this fails to compile. Not one, but several reasons. This entire dispatching mechanism must be completely redesigned.

The first order of business is that this is a

void run(void) const

A const class method.

The method pointer in question is:

void (Type::*f)(std::string const &);

The method pointer is not const, but mutable. From an existing const class method, you can only invoke other const methods. You cannot invoke non-const methods, either directly or indirectly via a method pointer, from a const class methods.

So the first order of business is to change this to

void (Type::*f)(std::string const &) const;

This also means that all your methods, in the child class, error() and success(), must also be const class methods too.

If it's necessary to use this dispatch mechanism with non-const methods, the run() method cannot be a const class method itself. But this is not the only problem here, so I'll continue with the const method, at hand.

return ((*(this).*ACTIONS[i].f)(m_str));

The this here, is a A<Type>. This is a method of that class. That's what this is here.

The method pointer, f is pointer to a method of Type, not A<Type>. Type is a subclass of A<Type>, and you cannot convert a pointer or a reference to a base class to a pointer or a reference to a subclass, any more than you can take a pointer to A, and convert to a pointer to B when B inherits from A. C++ does not work this way.

The solution is simple, and requires only a few small tweaks. This run() should take a reference to const Type &, and invoke the method via the passed-in reference, then a replacement abstract run() method invokes it, passing *this as a parameter:

public:
    virtual void run()=0;

protected:

    void run_me(const Type &me) const
        {
            unsigned int    i;

            for(i = 0; ACTIONS[i].f != nullptr; i++)
                if (m_str == ACTIONS[i].key)
                    return ((me.*ACTIONS[i].f)(m_str));
        }

Then, each subclass that inherits this template only needs to implement a simple facade:

class B : public A<B>
{

public:

    void run() const override
    {
        run_me(*this);
    }

EDIT: This addresses the compilation error, but additional work is needed to deal with the fact that static class members cannot be overridden. The solution is also pretty simple: also leverage virtual class methods in order to implement this.

Remove the declaration of ACTIONS from the template base class, and replace it with an abstract function:

virtual const t_action *get_actions() const=0;

And use it in run_me():

const t_action *ACTIONS=this->get_actions();

The rest of run_me() remains as is, and then implement get_actions() in the child class:

const t_action *get_actions() const override
{
    return ACTIONS;
}

Pretty much everything else remains the same.

Upvotes: 2

Matthieu Brucher
Matthieu Brucher

Reputation: 22023

The problem is that A will always use is own defined set of actions, not B's.

You don't need to create A at all, as you want to use B methods and list of methods.

Let's say that you create first a run call function:

template<typename T>
void run(T* obj, const std::string method)
{
    const auto& available_methods = obj->get_methods();
    auto iter = available_methods.find(method);
    if(iter == available_methods.end())
    {
        // Handle this case
    }
    std::invoke(iter->second, obj); //C++17, or (obj->*(iter->second))();
}

Now for the class B, you need something very simple:

class B
{
public:
    typedef std::unordered_map<std::string, void(B::*)()> MethodMap;

    void foo();

    static MethodMap& get_methods()
    {
        static MethodMap map{{"foo", &B::foo}};
        return map;
    }
};

Populate the map with get_methods() in the static function, and then call run through:

int main()
{
    B b;

    run(&b, "foo");
}

Upvotes: 1

Related Questions