Search code examples
c++c++03pointer-to-member

How to assign different member function pointers to different instances of registering classes?


EDIT: I am limited to C++03 on this topic.

In the following code, class Impl derives from Intf and contains an instance of class Caller.

Caller's ctor takes an Intf:: instance and member function pointer; it invokes the latter on the former in Caller::func().

Impl's ctor registers itself and its member function func() with its contained Caller instance.

I would like for Impl to contain multiple Caller instances and register different member function pointers with each instance so that invoking each contained Caller instance's ::func() results in a different Impl member function being called - can this be done?

The only way I could think to do this is to define multiple pure virtual functions in Intf, implement them in Impl and register those overriding functions. But this is not an ideal solution to me: I would like to know if there's a way to register different member function pointers with different instances of a registering class without creating virtual functions in the interface class 1:1 with overriding functions in the implementing class.

I need to exclude the possibility of Caller taking an Impl reference and member function; Caller can only know about Intfs and not Impls.

// main.cpp
#include <iostream>

class Intf
{
public:

  virtual void func() = 0;

  typedef void (Intf::*funcPtr)();
};

class Caller
{
public:

  Caller( Intf& f, Intf::funcPtr func ) : f_( f ), func_( func ) {}

  void func() { f_.func(); }

private:

  Intf& f_;
  Intf::funcPtr func_;
};

class Impl : public Intf
{
public:

  Impl()
    : c_ ( *this, &Intf::func )
//    , c2_( *this, static_cast<Intf::funcPtr>(func2) )
  {
  }

  void callCaller() { c_.func(); };

//  void callCaller2() { c2_.func(); };

private:

  void func()
  {
    std::cout << __FUNCTION__ << std::endl;
  }

  void func2()
  {
    std::cout << __FUNCTION__ << std::endl;
  }

  Caller c_;
//  Caller c2_;
};

int main( int argc, char* argv[] )
{
  Impl i;
  i.callCaller();
  return 0;
}

An additional question: could someone explain why in Impl's ctor, it is necessary to qualify the pointer to member function func() with Intf::? I.e. why is this correct...

Impl() : c_ ( *this, &Intf::func ) {}

...and why are these not correct...

Impl() : c_ ( *this, &Impl::func ) {}
Impl() : c_ ( *this, &func ) {}

?

The compiler error in the case of the '&Impl::func' version is:

g++ -g main.cpp && ./a.out

main.cpp: In constructor 'Impl::Impl()':
main.cpp:31:31: error: no matching function for call to 'Caller::Caller(Impl&, void (Impl::*)())'
     : c_ ( *this, &Impl::func )
                               ^
main.cpp:31:31: note: candidates are:
main.cpp:16:3: note: Caller::Caller(Intf&, Intf::funcPtr)
   Caller( Intf& f, Intf::funcPtr func ) : f_( f ), func_( func ) {}
   ^
main.cpp:16:3: note:   no known conversion for argument 2 from 'void (Impl::*)()' to 'Intf::funcPtr {aka void (Intf::*)()}'
main.cpp:12:7: note: Caller::Caller(const Caller&)
 class Caller
       ^
main.cpp:12:7: note:   candidate expects 1 argument, 2 provided

The compiler error in the case of the '&func' version is:

>g++ -g main.cpp && ./a.out
main.cpp: In constructor 'Impl::Impl()':
main.cpp:31:20: error: ISO C++ forbids taking the address of an unqualified or parenthesized non-static member function to form a pointer to member function.  Say '&Impl::func' [-fpermissive]
     : c_ ( *this, &func )
                    ^
main.cpp:31:25: error: no matching function for call to 'Caller::Caller(Impl&, void (Impl::*)())'
     : c_ ( *this, &func )
                         ^
main.cpp:31:25: note: candidates are:
main.cpp:16:3: note: Caller::Caller(Intf&, Intf::funcPtr)
   Caller( Intf& f, Intf::funcPtr func ) : f_( f ), func_( func ) {}
   ^
main.cpp:16:3: note:   no known conversion for argument 2 from 'void (Impl::*)()' to 'Intf::funcPtr {aka void (Intf::*)()}'
main.cpp:12:7: note: Caller::Caller(const Caller&)
 class Caller
       ^
main.cpp:12:7: note:   candidate expects 1 argument, 2 provided

Solution

  • You might use the old way callback:

    typedef void (*callback_t)(void*);
    

    an helper function:

    template <typename C, void (C::*M)()>
    void member_func(void* instance)
    {
        C* c = static_cast<C*>(instance); // Not typesafe :-/
        ((*c).*M)();
    }
    

    And then

    class Caller
    {
    public:
        Caller(Intf& f, callback_t func) : instance(f), func_(func) {}
    
        void func() const { func_(&instance); }
    private:
        Intf& instance;
        callback_t func_;
    };
    
    class Impl : public Intf
    {
    public:
        Impl()
          : c_  ( *this, &member_func<Intf, &Intf::func> )
          , c2_ ( *this, &member_func<Impl, &Impl::func2> )
      {
      }
    // ...
    };
    

    Demo

    Since C++11, you might replace Caller by std::function<void(void)> and initialize it by c2_([=](){ this->func2();}). and call it c2_();