20

In State.h I have

enum class StateID : unsigned int;

In State.cpp I have

enum class StateID : unsigned int
{
    NullID = 0,
    MainMenuID,
    GamePlayID,
};

The problem is that any class that includes State.h has the forward declaration, but I can't use any enum value within any cpp file except States.cpp ( which defined it ), like StateID::MainMenuID. The error says...

/home/lee/Projects/SuddenAwakening/Source/Game.cpp:24: error: 'MainMenuID' is not a member of 'StateID'

I'm running LinuxMint15KDE, g++ 4.7, and I am using c++11 features in other parts like nullptr, unique_ptr, ect..., so it's not that I forgot the compiler flag for c++11.

p.i.g.
  • 2,815
  • 2
  • 24
  • 41
EddieV223
  • 5,085
  • 11
  • 36
  • 38

1 Answers1

31

That's because only States.cpp knows what members exist inside enum class ID.

Files that include States.hpp only know that the enum class ID is the size of an unsigned int, and that is all.

You will need to make your enumeration values available in a header so that any file that does need to know about the enumerated values (e.g. MainMenuID) has them available.

You could make a separate header just for forwarding, maybe called StateFwd.hpp and then rename your State.cpp to State.hpp.

Example of where/how you might forward declare

I've updated my answer with an example, following the discussion we had in the comments.

fruit.hpp

Anyone who chooses to include this header will know what kind of fruits exist.

#ifndef FRUIT_HPP
#define FRUIT_HPP

enum class Fruit
{
    APPLE,
    ORANGE,
    BANANA
};

#endif

village.hpp

The village is full of people driven by their lust for fruit.

#ifndef VILLAGE_HPP
#define VILLAGE_HPP

enum class Fruit;

namespace farmer
{
    bool is_fruit_for_sale(Fruit fruit);
    float get_cost_of_fruit(Fruit fruit);
}

namespace blind_but_greedy_merchant
{
    bool sell_fruit(Fruit fruit, float money);
}

namespace peasant
{
    inline bool buy_fruit(Fruit fruit, float money)
    {
        return blind_but_greedy_merchant::sell_fruit(fruit, money);
    }
}

#endif

farmer.cpp

This farmer only grows apples and oranges, so he never has bananas for sale

#include "fruit.hpp"

namespace farmer
{
    bool is_fruit_for_sale(Fruit fruit)
    {
        switch ( fruit ) {
        case Fruit::APPLE:
        case Fruit::ORANGE:
            return true;
        case Fruit::BANANA:
            return false;
        }
        return false;
    }

    float get_cost_of_fruit(Fruit fruit)
    {
        switch ( fruit ) {
        case Fruit::APPLE:
            return 1.00f;
        case Fruit::ORANGE:
            return 2.50f;
        case Fruit::BANANA:
            return 200.0f;
        }
        return 0.0f;
    }
}

merchant.cpp

This merchant has gone blind from greed. He can no longer see what kind of fruit he is selling. He still knows how to deal with the farmer though, passing on customer requests to the farmer, while adding a steep profit margin for all fruit. This is why fruit.hpp is not included.

#include "village.hpp"

namespace blind_but_greedy_merchant
{
    bool sell_fruit(Fruit fruit, float money)
    {
        if ( !farmer::is_fruit_for_sale(fruit) ) {
            return false;
        }

        float inflatedcost = farmer::get_cost_of_fruit(fruit) * 3;

        if ( money < inflatedcost ) {
            return false;
        }

        return true;
    }
}

example.cpp

This pulls it all together. In our example, we want the peasant to go buy some fruit. We know exactly what kind of fruit we want; a BANANA. Therefore we need to include fruit.hpp, otherwise we could not tell the peasant to go and buy a BANANA for us.

The only two people in this scenario who know what kind of fruit exist are us (example.cpp) and the farmer (farmer.cpp). The peasant doesn't even need to know. It's like we've given him a folded piece of paper that says what fruit we want, but we've told him not to look at it. And he just hands it to the merchant, who can't read, so he just passes it on to the farmer.

#include "village.hpp"

#include "fruit.hpp"

int main()
{
    peasant::buy_fruit(Fruit::BANANA, 25.0f);

    return 0;
}
Timma
  • 663
  • 7
  • 19
  • According to many sites you can forward declare in the manner I am doing. Here is one such link. http://upto11blog.blogspot.com/2012/12/strong-enum-enum-class-strong-enum-enum.html – EddieV223 Sep 29 '13 at 04:07
  • @EddieV223 In the linked example's 'main program' they are demonstrating the purpose of forward declaring.The program knows there is such a thing as an Inventory::Fruit, but it still has no idea what kind of Fruits exist. But because it knows that there is such a thing as Fruit, it can get fruit, and get the cost of them. – Timma Sep 29 '13 at 04:20
  • How would you call float getCost(Fruit leFruit); without having access to the values of Fruit ? – EddieV223 Sep 29 '13 at 04:28
  • @EddieV223 They do exactly that in the example. They call leInventory.getFruit();. This gives us an Inventory::Fruit. We have no idea what value it is, or what values it could be. The only reason you can allocate one is because the compiler knows that an enum class is the same size as an int and the only thing you can do with it is write a Fruit value to it or read a Fruit value from it. The only other way to get a fruit is to construct one (Fruit myfruit;) or assign it by static casting some value: Fruit myfruit = static_cast(100); – Timma Sep 29 '13 at 04:46
  • 6
    +1 for the evocative example! "He can no longer see what kind of fruit he is selling... This is why fruit.hpp is not included" - genius. – Chowlett Apr 29 '14 at 15:30