Home > other >  Storing virual functions in Array/Vector container
Storing virual functions in Array/Vector container

Time:09-17

I have a base class with common behavior and derived classes with override virtual functions. But if tried to save the function pointer as std::vector I will get the error:

 ISO C   forbids taking the address of an unqualified or
  parenthesized non-static member function to form a pointer to member function.

I need something like this:

class Base
{
public:
    virtual int fun1(int inp) = 0;
    virtual int fun2(int inp) = 0;
    int init(int inp, std::vector<std::function<int(int)>> Callbacks, int index)
    {
        if (index == 0)
            return Callbacks[0](inp);
        else
            return Callbacks[1](inp);
    }
    int run()
    {
        return init(5, { &fun1, &fun2 }, 0);
    }
};

class A : public Base
{
    int fun1(int inp)
    {
        return inp * 10;
    }
    int fun1(int inp)
    {
        return inp * 100;
    }
};

class B : public Base
{
    int fun1(int inp)
    {
        return inp * 20;
    }
    int fun2(int inp)
    {
        return inp * 200;
    }
};

int main(int argc, char const* argv[])
{
    auto f = new B;
    int e = f->run();
    return 0;
}

Is there any way to do what I want? Maybe I can bind a virtual function to a container somehow? Or maybe can I set the lambda function as virtual? When I tried to declare lambda function in the Base class when I change function in derived class I get errors:

CodePudding user response:

Your code has several issues:

  1. Pointers to member functions are not the same as pointers to non-member, or static, or "free" functions. That means the type here std::function<int(int)> is wrong as it meant for free functions or static functions.

  2. The call to the member function pointer is not the same as the normal function call. You need to specify the object to which you are calling the member function pointer. Since C 17 you can also invoke the pointer to member function using more generic std::invoke.

  3. You only need to init the Callbacks once (probably in the contractor), and use it in run later.

  4. Upon passing the member function pointer, you need the syntax &ClassName::MemberFunction, which you have missed in the function call init(5, {&fun1, &fun2}, 0);.

  5. The Base required a virtual destructor for defined behavior, in case you store the children in the Base class pointer at a later point.

Following is the example code as per the above changes:

#include <functional> // std::invoke

class Base
{
    // member function pointer type
    using MemFunPtrType = int(Base::*)(int);
    // to store the member functions pointers
    std::vector<MemFunPtrType> mMemberFunctionPts; 

public:
    Base() // initlize the mMemberFunctionPts once!
        : mMemberFunctionPts{ { &Base::fun1, &Base::fun2 } }
    {}
    virtual int fun1(int inp) = 0;
    virtual int fun2(int inp) = 0;
    virtual ~Base() = default;

    // run choose the correct member function as per the passed index!
    int run(std::size_t index)
    {
        if (2u == mMemberFunctionPts.size() && index == 0)
            return (this->*mMemberFunctionPts[index])(5);
            // or using std::invoke
            // return std::invoke(mMemberFunctionPts[index], this, 5);
        else
            return (this->*mMemberFunctionPts[index])(5);
    }
};

See a (Live Demo Here)

CodePudding user response:

You get a "pointer" to a member with the syntax &Class::Member (Class::Member is the "qualified" name of the member).

These are pointers only in an abstract sense, and need to be dereferenced relative to an object.
You do this with the ->* or .* operator, depending on whether the left-hand side is a pointer or not.
Note that you must use this explicitly, if that is the relevant object.

The type of &Base::fun1 and &Base::fun2 is int (Base::*)(int), which can't be converted to std::function<int(int)>.

Putting it together:

int init(int inp, std::vector<int (Base::*)(int)> Callbacks, int index)
{
    if (index == 0)
        return (this->*Callbacks[0])(inp); // The leftmost parentheses are necessary.
    else
        return (this->*Callbacks[1])(inp);
}

int run()
{
    return init(5, {&Base::fun1, &Base::fun2}, 0);
}

If you don't want to limit the callbacks to members, you can use lambda functions, capturing this:

int init(int inp, std::vector<std::function<int(int))> Callbacks, int index)
{
    if (index == 0)
        return Callbacks[0](inp);
    else
        return Callbacks[1](inp);
}

int run()
{
    return init(5, 
                {[this](int i) { return fun1(i); },
                 [](int) { return 567; } }, 
                0);
}

CodePudding user response:

@PaulMcKenzie, thanks. Now it works:

class Base
{
public:
    std::function<int(int)> fun1;
    std::function<int(int)> fun2;
    int init(int inp, std::vector<std::function<int(int)>> Callbacks, int index)
    {
        if (index == 0)
            return Callbacks[0](inp);
        else
            return Callbacks[1](inp);
    }

    int run()
    {
        return init(5, {&fun1, &fun2}, 0);
    }
};

class B : public Base
{
public:
    B()
    {
        fun1 = fun1B;
        fun2 = fun2B;
    }

private:
    std::function<int(int)> fun1B = [&](int inp)
    {
        return inp * 20;
    };
    std::function<int(int)> fun2B = [&](int inp)
    {
        return inp * 200;
    };
};

int main(int argc, char const *argv[])
{
    auto f = new B();
    int e = f->run();
    assert(e == 5*20);
    return 0;
}

But the example above looks more correct.

  • Related