0

I have a templated base class that I use as a pseudo-state-machine kind of object. It has a primary method Progress_State() that (after error checking) steps through an enum (provided as one of the template arguments), and runs a handler for whichever state it is in. The problem is that I have to use a this pointer to reference the Event_Handler array, which is an array of derived-class function pointers (i.e. Event_Handlers[i] = Derived_Class::*).

The function definitions were in a separate XX.cpp file that included the XX.h file (where the template classes were defined). However, since all the functions were templated functions, I was ending up with unresolved symbols. Upon some research, I discovered that the templated function definitions need to be accessible to the compiler when compiling files that use the template class, and so I've renamed the XX.cpp to an XX.tpp and put in an #include "XX.tpp" in the bottom of the "XX.h." It had compiled before without an issue, but just that change caused compile errors. Most notably, I'm getting an "expression must have pointer type" error in the line where I try and call the stored function pointer.

Templates.h

/*
 * derived_class = is the derived class type, used for correctly 
 * using the function pointers
 * event_type = the enum that we're using as "sequencer"
 * max_events = the "max_XX" enum, used to set our handler array size
 */
template< derived_class, event_type, max_events >
Class Base{

// Set up the typedef for the derived class function pointer
   typedef int(derived_class::*PFN)(void);

public:
    // ....
   int Progress_State();
    // ....

private:
   // ....
   PFN           Event_Handlers[Num_Events];
   event_type    Current_State;
   // ....       

};

// Bring in the templated function definitions
#include "Templates.tpp"

Templates.tpp

template< derived_class, event_type, max_events >
int Base::Progress_State(){

   // Various error checking stuff, including whether or not we're
   // at the end of the sequence, or if we have a null handler

   // If no errors, continue.
   // FYI: 0 indicates no errors

   if( 0 == this->*Event_Handlers[Current_State]() ){ // <--* error happens here

      // Step sequence -- some funkiness here to allow for incrementing
      // of the sequence without overloading the ++ operator for every
      // enum we intend to track, while also protecting against 
      // overruns

      Current_State = static_cast<event_type>( 
          (static_cast<int>(Current_Event) + 1) % max_events
       );
   }

   // ... do other stuff as needed ...
}

I suspect that this is happening because the "this" pointer is actually pointing to some kind of derived class, but I can't figure out a way around it.

Here are a few other things I've tried:

The following lines generate the following error:

OK == this->*Event_Handlers[Current_State]();
OK == *Event_Handlers[Current_State]();
(*this).*Event_Handlers[Current_State]();

error: "expression must have (pointer-to-) function

The expected functionality would be to just call the registered handler function (set separately) and step the sequence.

Thanks in advance

andrewec
  • 136
  • 9
  • 1
    Seems, at the very least, relevant, if not duplicate: [Calling C++ class methods via a function pointer](https://stackoverflow.com/questions/1485983/calling-c-class-methods-via-a-function-pointer), because you are not calling class methods, via function pointers, correctly. – Algirdas Preidžius Dec 31 '18 at 18:16
  • That was a typo on my part -- thank you for the catch. – andrewec Dec 31 '18 at 18:22
  • You should include the compiler error message in your question. – super Dec 31 '18 at 18:37
  • the compiler errors are already in the question -- see anywhere I've written "error: ..." – andrewec Dec 31 '18 at 18:53
  • 1
    People expect code blocks to contain code, and comments therein to be comments on the code... not error messages that you couldn't be bothered putting in proper quote markdown. ;-) So it's understandable if they skim over such "comments". – underscore_d Dec 31 '18 at 19:34
  • That is a very fair point -- I had thought that it would be simpler to read the error alongside the code that generated it. I'll bear that in mind for the future :) – andrewec Dec 31 '18 at 20:54

2 Answers2

1

I see the following problem in your code:

You try to call a member function of a derived class inside the base class with this which is not a compatible pointer type. You have first to cast to the pointer type of the derived class. You "know" that this is a pointer of the derived class, as you are using the CRTP pattern here. https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern

With a simplified example the call is something like:

template < typename Derived >
class Base
{
    public:
        using PTR_T = void(Derived::*)();
        PTR_T ptr;

        Base( PTR_T _ptr ) : ptr {_ptr}{}

        void Call()
        { 
        // the important thing here is the cast to the derived type of "this"
            (((Derived*)this)->*ptr) ();
        }
};

class Derived: public Base< Derived >
{
    public:
        void F() { std::cout << "Hello" << std::endl; }

        Derived():Base{ &Derived::F } {}
};  

int main()
{   
    Derived d;
    d.Call();
}
Klaus
  • 24,205
  • 7
  • 58
  • 113
  • Of course! That makes total sense, and sure enough, it worked. Of course, it would help if I remembered the () around the function pointer itself (prior to the args list). Thanks! – andrewec Dec 31 '18 at 19:00
  • 1
    @andrewec: The pointer syntax is a bit "special". I hope that is your last problem of the year :-) I wish you a perfect 2019! – Klaus Dec 31 '18 at 19:03
  • Indeed, and this is the first attempt I've had at using function pointers in a CRTP pattern...a good solution to end 2018! Happy New Year! – andrewec Dec 31 '18 at 19:04
0

It looks like you are searching for std::invoke. This is the functionality that MSVC used already for a while to deal with callable objects. (_INVOKE before available in the standard).

 auto return value = std::invoke(func, arg1, arg2, ...);

For your case, you could call this as:

 auto errorCode = std::invoke(EventHandlers[Current_State], *this); 
JVApen
  • 11,008
  • 5
  • 31
  • 67
  • Sounds like exactly something I'd be looking for, but unfortunately my std library collection is a bit outdated...I'm using an older install of VxWorks for my libraries, and I have no way of upgrading. – andrewec Dec 31 '18 at 18:50
  • @andrewec Any chance you could import the libcxx implementation somehow in your codebase? – JVApen Dec 31 '18 at 18:51
  • I'm curious why this method was added -- isn't this essentially just doing the same thing as `*this.*EventHandlers[Current_State]()` but with (possibly) a bit more stuff under the hood? – andrewec Dec 31 '18 at 18:51
  • Unfortunately no. We're stuck with the library setup we have. – andrewec Dec 31 '18 at 18:52
  • 1
    Actually, this method was introduced to choose between the overloads. The first argument should be a callable, regardless of this being a member function, free function, lambda ... – JVApen Dec 31 '18 at 18:54