62

Coming from a Java background, I find C++'s enums very lame. I wanted to know how to write Java-like enums (the ones in which the enum values are objects, and can have attributes and methods) in C++.

For example, translate the following Java code (a part of it, sufficient to demonstrate the technique) to C++ :

public enum Planet {
    MERCURY (3.303e+23, 2.4397e6),
    VENUS   (4.869e+24, 6.0518e6),
    EARTH   (5.976e+24, 6.37814e6),
    MARS    (6.421e+23, 3.3972e6),
    JUPITER (1.9e+27,   7.1492e7),
    SATURN  (5.688e+26, 6.0268e7),
    URANUS  (8.686e+25, 2.5559e7),
    NEPTUNE (1.024e+26, 2.4746e7);

    private final double mass;   // in kilograms
    private final double radius; // in meters
    Planet(double mass, double radius) {
        this.mass = mass;
        this.radius = radius;
    }
    private double mass()   { return mass; }
    private double radius() { return radius; }

    // universal gravitational constant  (m3 kg-1 s-2)
    public static final double G = 6.67300E-11;

    double surfaceGravity() {
        return G * mass / (radius * radius);
    }
    double surfaceWeight(double otherMass) {
        return otherMass * surfaceGravity();
    }
    public static void main(String[] args) {
        if (args.length != 1) {
            System.err.println("Usage:  java Planet <earth_weight>");
            System.exit(-1);
        }
        double earthWeight = Double.parseDouble(args[0]);
        double mass = earthWeight/EARTH.surfaceGravity();
        for (Planet p : Planet.values())
           System.out.printf("Your weight on %s is %f%n",
                             p, p.surfaceWeight(mass));
    }
}

Any help would be greatly appreciated!

Thanks!

einpoklum
  • 118,144
  • 57
  • 340
  • 684
missingfaktor
  • 90,905
  • 62
  • 285
  • 365
  • 1
    As this appears to be a popular question, consider citing the original: http://java.sun.com/j2se/1.5.0/docs/guide/language/enums.html – trashgod Dec 27 '09 at 17:00
  • I had cited it here in the comments. I do not have any idea who deleted the comments here. – missingfaktor Dec 27 '09 at 17:05
  • I am going to expand my answer. I think the answer you got was likely better. But I think my answer is still illustrative of an interesting technique. – Omnifarious Dec 27 '09 at 18:45

4 Answers4

79

One way to simulate Java enums is to create a class with a private constructor that instantiates copies of itself as static variables:

class Planet {  
  public: 
    // Enum value DECLARATIONS - they are defined later 
    static const Planet MERCURY;  
    static const Planet VENUS;  
    // ... 

  private: 
    double mass;   // in kilograms  
    double radius; // in meters  

  private: 
    Planet(double mass, double radius) {  
        this->mass = mass;  
        this->radius = radius;  
    } 

  public: 
    // Properties and methods go here 
}; 

// Enum value DEFINITIONS 
// The initialization occurs in the scope of the class,  
// so the private Planet constructor can be used. 
const Planet Planet::MERCURY = Planet(3.303e+23, 2.4397e6);  
const Planet Planet::VENUS = Planet(4.869e+24, 6.0518e6);  
// ... 

Then you can use the enums like this:

double gravityOnMercury = Planet::MERCURY.SurfaceGravity();
Anton
  • 3,170
  • 20
  • 20
  • 4
    that is also pretty much the code that Java generates under the hood. – TofuBeer Dec 27 '09 at 07:40
  • 5
    unfortunately some of the java features are still missing like iteration over enum values automatic name/toString function... but it is still a nice implementation. – João Portela Jan 11 '10 at 18:26
  • 5
    How about storing a private vector of *Planet. And in the private Planet constructor, add this to the vector. Then one can iterate over the enum. – Ant6n Jun 30 '14 at 05:36
  • You could also make "mass" and "radius" const, to be closer to the java example. But they then have to be initialized in the initializer list (and therefore only possible with C++ 11 or higher I guess) – deetz Nov 14 '17 at 09:55
  • What to do, if we want to use such enum in copy assignment operator? Or referencing by pointer is the only one option? – tr1cks Aug 28 '18 at 12:01
  • 1
    Great solution. If in addition, you'd implement the `operator int()`, like in the answer https://stackoverflow.com/a/29594977/2780179 below, you can use this enum in a switch statement as well. – MathKid May 12 '21 at 11:09
10

With C++11's introduction of constexpr. There's yet another way to implement typed enums. One that works practically the same as normal enums (is stored as an int variable and can be used in a switch statement), but also allows them to have member functions.

In the header file you would put:

class Planet {
    int index;
public:
    static constexpr int length() {return 8;}
    Planet() : index(0) {}
    constexpr explicit Planet(int index) : index(index) {}
    constexpr operator int() const { return index; }

    double mass() const;
    double radius() const;

    double surfaceGravity() const;
};
constexpr Planet PLANET_MERCURY(0);
constexpr Planet PLANET_VENUS(1);
constexpr Planet PLANET_EARTH(2);
// etc.

And in the source file:

static double G = 6.67300E-11;

double Planet::mass() {
    switch(index) {
        case PLANET_MERCURY: return 3.303e+23;
        case PLANET_VENUS: return 4.869e+24;
        case PLANET_EARTH: return 5.976e+24;
        // Etc.
    }
}

double Planet::radius() {
    // Similar to mass.
}

double Planet::surfaceGravity() {
    return G * mass() / (radius() * radius());
}

Which can then be used as:

double gravityOnMercury = PLANET_MERCURY.SurfaceGravity();

Unfortunately, the enum entries cannot be defined as static constants within the class body. They must be initialized upon declaration, because they are constexpr, but inside the class, the class is not yet a complete type and thus cannot be instantiated.

bcmpinc
  • 3,202
  • 29
  • 36
  • I like this! Is there a way of avoiding the hard coding of indices and the length though? I am looking for enum "class" in my quest for finding a solution to this problem: http://stackoverflow.com/questions/39687392/use-of-enums-for-indexing-arrays-in-a-relay-table – Maths noob Sep 25 '16 at 22:03
  • 1
    Probably, you can use global variables and initialize those during program startup. Though this adds a lot of unnecessary complexity. If the enum entries are not known at compile time, then you should not be using an enum. A lookup table or std::map would be more appropriate in that case. – bcmpinc Sep 27 '16 at 01:09
  • The downside of this solution is that implementation is not object oriented. If a planet needs to be added or removed, the two very similar switch statements for mass() and radius() need to be modified. If you forget this you could face a runtime error. With this anwer https://stackoverflow.com/questions/1965249/how-to-write-a-java-enum-like-class-with-multiple-data-fields-in-c/1965344#1965344 you get compile time safety. – MathKid May 12 '21 at 11:39
  • Enums aren't object oriented. You should be able to list all possible values in the file defining the enum. If you need to be able to extend them, then you should not be using an enum. The issue with that other solution is that it cannot be used in a switch statement and you cannot loop over all possible enum values. If you want a compile time safety, implement the lookup functions with a lookup table and add static assertions. (This is what I did) – bcmpinc May 13 '21 at 14:22
2

This is ugly, verbose, and generally a dumb way to go. But I figured I'd post a complete code example by way of explanation. For extra points it's actually possible to define a compile-time expanded iteration over the solar planets by tweaking the template specializations just a tad.

#include <string>
#include <sstream>
#include <iostream>
#include <cstdlib>

class Planet {
 public:
   static const double G = 6.67300E-11;

   Planet(const ::std::string &name, double mass, double radius)
        : name_(name), mass_(mass), radius_(radius)
      {}
   const ::std::string &name() const { return name_; }
   double surfaceGravity() const {
      return G * mass_ / (radius_ * radius_);
   }
   double surfaceWeight(double otherMass) const {
      return otherMass * surfaceGravity();
   }

 private:
   const ::std::string name_;
   const double mass_;
   const double radius_;
};

enum SolarPlanets {
   MERCURY,
   VENUS,
   EARTH,
   MARS,
   JUPITER,
   SATURN,
   URANUS,
   NEPTUNE
};

template <SolarPlanets planet>
class SolarPlanet : public Planet {
};

template <>
class SolarPlanet<MERCURY> : public Planet {
 public:
   SolarPlanet() : Planet("MERCURY", 3.303e+23, 2.4397e6) {}
};

template <>
class SolarPlanet<VENUS> : public Planet {
 public:
   SolarPlanet() : Planet("VENUS", 4.869e+24, 6.0518e6) {}
};

template <>
class SolarPlanet<EARTH> : public Planet {
 public:
   SolarPlanet() : Planet("EARTH", 5.976e+24, 6.37814e6) {}
};

template <>
class SolarPlanet<MARS> : public Planet {
 public:
   SolarPlanet() : Planet("MARS", 6.421e+23, 3.3972e6) {}
};

template <>
class SolarPlanet<JUPITER> : public Planet {
 public:
   SolarPlanet() : Planet("JUPITER", 1.9e+27, 7.1492e7 ) {}
};

template <>
class SolarPlanet<SATURN> : public Planet {
 public:
   SolarPlanet() : Planet("SATURN", 5.688e+26, 6.0268e7) {}
};

template <>
class SolarPlanet<URANUS> : public Planet {
 public:
   SolarPlanet() : Planet("URANUS", 8.686e+25, 2.5559e7) {}
};

template <>
class SolarPlanet<NEPTUNE> : public Planet {
 public:
   SolarPlanet() : Planet("NEPTUNE", 1.024e+26, 2.4746e7) {}
};

void printTerranWeightOnPlanet(
   ::std::ostream &os, double terran_mass, const Planet &p
   )
{
   const double mass = terran_mass / SolarPlanet<EARTH>().surfaceGravity();
   os << "Your weight on " << p.name() << " is " << p.surfaceWeight(mass) << '\n';
}

int main(int argc, const char *argv[])
{
   if (argc != 2) {
      ::std::cerr << "Usage: " << argv[0] << " <earth_weight>\n";
      return 1;
   }
   const double earthweight = ::std::atof(argv[1]);
   printTerranWeightOnPlanet(::std::cout, earthweight, SolarPlanet<MERCURY>());
   printTerranWeightOnPlanet(::std::cout, earthweight, SolarPlanet<VENUS>());
   printTerranWeightOnPlanet(::std::cout, earthweight, SolarPlanet<EARTH>());
   printTerranWeightOnPlanet(::std::cout, earthweight, SolarPlanet<MARS>());
   printTerranWeightOnPlanet(::std::cout, earthweight, SolarPlanet<JUPITER>());
   printTerranWeightOnPlanet(::std::cout, earthweight, SolarPlanet<SATURN>());
   printTerranWeightOnPlanet(::std::cout, earthweight, SolarPlanet<URANUS>());
   printTerranWeightOnPlanet(::std::cout, earthweight, SolarPlanet<NEPTUNE>());
   return 0;
}
Omnifarious
  • 54,333
  • 19
  • 131
  • 194
1

May be this is what you want --

#include<iostream>

using namespace std;

class Planet {
    double mass,radius;

    Planet(double m, double r) : mass(m) : radius(r) {}

public:
    static const Planet MERCURY;

    void show(){
        cout<<mass<<","<<radius<<endl;
    }
} ;
const Planet Planet::MERCURY = Planet(1.0,1.2);

int main(){
    Planet p = Planet::MERCURY;
    p.show();
}

This is just a small code, Im sure you can modify this to suit your needs..

UltraInstinct
  • 43,308
  • 12
  • 81
  • 104