C++ Access to not yet defined member of template parameter

Viewed 94

Doing some Metaprogramming with c++11.

I want to build an EnumWrapper which can be used for switch statements and as keywords for maps but still has object-like properties, like Java Enumerations.

Having the classes:

#include <string>
#include <vector>

template<typename T, int len>
class Enum
{
  private:
    typename T::Value value; // invalid use of incomplete Type Planet

  public:
    Enum() = delete;

    constexpr Enum( typename T::Value key ) : value( key ) // invalid use of incomplete Type Planet
    {
    }

    // Allow switch and comparisons.
    operator typename T::Value() const // invalid use of incomplete Type Planet
    {
        return value;
    }

    T& operator=( const T& other )
    {
        this->value = other.value;
        return *this;
    }

    inline typename T::Value getValue() // invalid use of incomplete Type Planet
    {
        return value;
    }

    explicit operator bool() = delete;

    Enum& operator=( const Enum& p )
    {
        this->value = p.value;
        return *this;
    }

    static inline int length()
    {
        return len;
    }

    static std::vector<T> values()
    {
        std::vector<T> values;
        values.reserve( length() );

        for ( int i = 0; i < length(); i++ )
        {
            values.push_back( (typename T::Value)i ); // This is fine for some reason
        }

        return values;
    }

    static T getMember( std::string name )
    {
        for ( T p : values() )
        {
            if ( p.name() == name )
            {
                return p;
            }
        }
    }
};


class Planet : Enum<Planet, 3>
{
  public:
    enum Value
    {
        MERCURY,
        VENUS,
        EARTH
    }
    
    std::string name()
    {
         switch(this->getValue())
         {
             case MERCURY:
                 return "Mercury";
             case EARTH:
                 return "Mercury";
             case VENUS:
                 return "Mercury";
         }
    }
};

The compiler gives several "invalid use of incomplete Type Planet" messages.

When merging the classes into one single class, the code compiles and works as intendet. But reuseablility and maintainability is not given with a merged approach.

Can someone help me with this error messages?

Just tell me, if further information is needed or wanted.

Edit: My inteded use is something like this:

int main()
{
    Planet planet        = Planet::EARTH;
    Planet invalidPlanet = Planet( (Planet::Value)5 );

    // attributes with enum working
    std::cout << "My Planet is named " << planet.name() << std::endl;
    Planet venus = Planet::VENUS;
    std::cout << "Gravity of Venus is " << venus.gravitation() << std::endl;

    // Switch working
    switch ( planet )
    {
    case Planet::VENUS:
        std::cout << "Venus found." << std::endl;
        break;
    case Planet::EARTH:
        std::cout << "Earth found." << std::endl;
        break;
    default:
        std::cout << "Nothing found." << std::endl;
        break;
    }

    // simple comparison
    bool isEarth = planet == Planet::EARTH; // true
    bool isVenus = planet == Planet::VENUS; // false

    std::cout << "Planet is earth: " << ( isEarth ? "true" : "false" ) << std::endl;
    std::cout << "Planet is venus: " << ( isVenus ? "true" : "false" ) << std::endl;

    // works in Map
    std::map<Planet, std::string> myMap;
    myMap[ planet ] = "MyEarth";
    
    std::cout << "chosen planet: " << myMap[ Planet::MERCURY ] << std::endl;
    std::cout << "chosen planet: " << myMap[ Planet::EARTH ] << std::endl;

    // Map is not bound to object but bound to enum integer
    Planet fakeEarth = Planet::EARTH;
    std::cout << "chosen planet: " << myMap[ fakeEarth ] << std::endl;

    return 1;
}
1 Answers

The problem is that a class is considered incomplete until its closing brace, except in the bodies of its member functions. Your Planet class depends on the definition of its base class Enum being available. However, the base class depends on T::Value outside of a member function body, so you have a scenario where the class Planet ends up depending on the definition of class Planet being available.

You can split out your enum from the Planet class and pass it as a separate template parameter to the base like so:

template<class ENUM, typename T, int len>
class Enum
{
  private:
   ENUM value;
//...
};

enum Planets
{
    MERCURY,
    VENUS,
    EARTH
};

class Planet : public Enum<Planets, Planet, 3>
{ /*...*/}

Live Demo


Another thing to worry about is that Enum::getMember may exit without returning anything. I recommend you modify it to throw like so:

static T getMember( std::string name )
{
    for ( T p : values() )
    {
        if ( p.name() == name )
        {
            return p;
        }
    }
    throw std::invalid_argument("Could not find" + name);
}

Other minor things:

  • you accidentally used the name "Mercury" for all three enum values
  • prefer enum class (enum class Planets)
  • prefer to static_cast instead of C-style cast (static_cast<ENUM>(i)))
  • Your assumption that enumerated values will start at zero and increase by 1 is brittle
  • the need to manually pass the number of values in the enum is brittle
  • It's weird that a effectively an instance of an enum has a method like getValue that will return you the actual enum held, but also mixes in methods like values and length that are for getting information about the type of enum being held; it could be confusing
  • Hopefully when we get metaclasses in C++ all this will become much, much easier.

Updated demo

Related