4

Here is a simplified version of the code I am trying to write:

template<typename Derived>
class StateMachine
{
public:
  void SetState(Derived::State s) {
    static_cast<Derived*>(this)->TransitionTo(s);
  }
};

class MyFSM : public StateMachine<MyFSM>
{
public:
  enum class State {
    State1,
    State2,
    State3
  };
  void TransitionTo(State s) {
    _state = s;
  }
private:
  State _state = State::State1;  
};

I'm using c++11 with clang. The error I get here is 10:17: error: missing 'typename' prior to dependent type name 'Derived::State' for the declaration of SetState. I've also tried adding typename Derived::State DerivedState; and then using DerivedState instead of Derived::State, but then I get error: unknown type name 'DerivedState'.

More confusing still, I tried typedef typename Derived::State DerivedState; and then I get the error: error: no type named 'State' in 'MyFSM'. my last try was typedef enum class Derived::State DerivedState; and then I get the most confusing error of all: error: no enum named 'State' in 'MyFSM'.

I'm out of my depth with templates here and would welcome any help understanding this! Also, there may be a better pattern to use here. The main thing I'm going for is that the StateMachine class has some common functionality and then there are a bunch of different state machines, each with their own state and transition function. I have another way of doing it that requires passing in this to SetState (when called from within MyFSM, which is a simplified version of what I'm doing), but that's ugly for it's own reason. I'd love to avoid this tempalted class thing, but my main goal is making the derived classes as easy to write and understand as possible.

stokastic
  • 986
  • 2
  • 7
  • 19

4 Answers4

5

You may delay the type deduction by making SetState a template:

#include <type_traits>

template<typename Derived>
class StateMachine
{
public:
  template <typename State>
  void SetState(State s) {
    static_assert(std::is_same<State, typename Derived::State>::value, "Not a derived state");
    static_cast<Derived*>(this)->TransitionTo(s);
  }
};

class MyFSM : public StateMachine<MyFSM>
{
public:
  enum class State {
    State1,
    State2,
    State3
  };
  void TransitionTo(State s) {
    _state = s;
  }
private:
  State _state = State::State1;
};

int main() {
    MyFSM fsm;
    fsm.SetState(MyFSM::State::State1);
    // error: static assertion failed: Not a derived state
    // fsm.SetState(0);
}
3

Using

void SetState(typename Derived::State s) {

does not work since the complete definition of Derived is not known at that time.

You can use a slightly refactored code base to accomplish what you are trying.

#include <iostream>

using namespace std;

template<typename Derived, typename DerivedState>
class StateMachine
{
   public:
      void SetState(typename DerivedState::State s) {
         static_cast<Derived*>(this)->TransitionTo(s);
      }
};

class MyFSMState
{
   public:
      enum class State {
         State1,
         State2,
         State3
      };
};

class MyFSM : public MyFSMState, public StateMachine<MyFSM, MyFSMState>
{
   public:
      void TransitionTo(State s) {
         _state = s;
      }
   private:
      State _state = State::State1;  
};

int main()
{
   MyFSM v;
   v.TransitionTo(MyFSM::State::State2);
}
R Sahu
  • 204,454
  • 14
  • 159
  • 270
2

When the compiler gets to public StateMachine<MyFSM> in class MyFSM : public StateMachine<MyFSM> it tries to stamp out StateMachine<MyFSM>. When it does that MyFSM::State(Derived::State) has not yet been seen. Since the compiler does not know about that type it issues the error.

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
1

As mentioned in other answers, the problem is Derived is not full known when the CRTP Base is instantiated.

One option therefore is to defer the determination of the type you're interested in like this:

template<typename Derived, typename = void>
struct DeferredState
{
  using type = typename Derived::State;
};

template <typename Derived>
class StateMachine
{
public:
  template<typename T = void>
  void SetState(typename DeferredState<Derived, T>::type s)
  {
    static_cast<Derived*>(this)->TransitionTo(s);
  }
};

Since you could specialize the DeferredState template, the compiler has no option but to wait until you actually call SetState before determining the argument type. At that time, Derived is fully defined. And your problem is gone.

Rumburak
  • 3,416
  • 16
  • 27