2

I have created a class Base which has a function addSuccessor() that takes address of function as argument and stores it in successor. How do I pass a function of another object in addSuccessor(). Here is my program. I think my main() has some mistake.

#include <iostream>
#include<vector>
using namespace std;
class Base{
public:
    void (*successor)()=NULL;
    void addSuccessor ( void (*f)() )
    {
        successor=f;
    }
    void start()
    {
        cout<<"In Class"<<endl;
        if(!successor==NULL)
            successor();
        else
            cout<<"No Successor"<<endl;
    }
};
class Second{
    public:
    void foo()
    {
        cout<<"Successor";
    }
};

int main()
{
    Base obj;
    Second obj2;

    obj.addSuccessor(&obj2.foo());
    obj.start();

}
H.S.
  • 11,654
  • 2
  • 15
  • 32
Lengdon
  • 79
  • 7
  • 1
    `foo()` is not a function. It is a non-static class method. You cannot convert non-static class methods directly to function pointers in C++. C++ does not work this way. You need to understand that, and the reason for that; and for this [you need a good C++ book](https://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list). – Sam Varshavchik Feb 17 '20 at 04:34
  • Since it's 2020 why not use [C++ lambdas](https://en.cppreference.com/w/cpp/language/lambda) instead of function pointers? – tadman Feb 17 '20 at 04:35

2 Answers2

3

Function pointers are very limited. Use a std::function (defined in header <functional>) instead, which can store any invocable object in a type-erased manner, and provide a template member function to set the functor in which perfect forwarding is used to forward the provided functor to successor: (std::forward is defined in header <utility>)

class Base {
    std::function<void()> successor;
public:
    template <typename F>
    void add_successor(F&& f)
    {
        successor = std::forward<F>(f);
    }
    void start()
    {
        if (successor) {
            successor();
        } else {
            std::cout << "No Successor\n";
        }
    }
};

Then, you can pass a lambda expression to add_successor:

obj.add_successor([&]{ obj2.foo(); });

(live demo)

L. F.
  • 19,445
  • 8
  • 48
  • 82
  • You can also use `std::invoke`. ```if (successor) { std::invoke(successor); } else { std::cout << "No Successor\n"; }``` – Soumya Kanti Feb 17 '20 at 04:59
  • @SoumyaKanti [`std::function::operator()`](https://en.cppreference.com/w/cpp/utility/functional/function/operator()) internally uses `INVOKE` to invoke the stored function object, so we can enjoy the intuitive syntax :) – L. F. Feb 17 '20 at 05:03
  • What if the signature of `foo` member function in class `Second` is `int foo(int)`? – Soumya Kanti Feb 17 '20 at 05:33
  • @SoumyaKanti You can provide the argument in the lambda expression. – L. F. Feb 17 '20 at 05:56
  • Actually, successor's signature is known and static, so isn't it better to remove template ? – calynr Feb 17 '20 at 11:50
  • @arnes How will you specify the type of the lambda in the class then? – L. F. Feb 17 '20 at 11:52
  • Exactly.Here `F` is the type of the lambda. – Soumya Kanti Feb 17 '20 at 11:56
  • What's wrong with `void add_successor(const std::function& f)` ? – calynr Feb 17 '20 at 12:01
  • @arnes Umm ... well, you have to construct `std::function` twice then, which is expensive. I am trying to avoid type erasure unless absolutely necessary. – L. F. Feb 17 '20 at 12:03
  • 1
    I am not sure that it is constructed twice but at least we could add something like `static_assert( std::is_invocable::value , "F's signature should be 'void(*)()'" );` for friendlier error message. – calynr Feb 17 '20 at 12:12
  • 1
    @arnes Yeah, or we can do SFINAE. Or we can wait for C++20; `template ` looks better. – L. F. Feb 17 '20 at 12:22
1

As a complement to the discussion above (based on @L.F.'s answer):

#include <functional>
#include <iostream>
#include <utility>

class Base {
    std::function<int(int)> successor;
public:
    template <typename F>
    void add_successor(F&& f)
    {
        successor = std::forward<F>(f);
    }
    void start()
    {
        if (successor) {
            auto z = std::invoke(successor, 100);
            std::cout << "Output = " << z << "\n";
        } else {
            std::cout << "No Successor\n";
        }
    }
};

class Second {
public:
    int foo(int x)
    {
        auto y = x + x;
        std::cout << "Successor\n";
        return y;
    }
};

int main()
{
    Base obj;
    Second obj2;

    obj.add_successor([&](int x)->int { return obj2.foo(x); });
    obj.start();
}
Soumya Kanti
  • 1,429
  • 1
  • 17
  • 28