12

I'm looking for a hacky kind of solution to the following problem: GCC 4.4+ accepts the following c++0x code:

enum class my_enum
{
    value1,
    value2
};

Which allows the use like this:

my_enum e = my_enum::value1;

with all the bells and whistles this brings. I would like to make this code compatible with MSVC 2010, to the effect that the usage syntax does not change. I already pondered on this before here, and the accepted answer works, but the need for the two different names fo the enum and the enum values is killing the compatibility of the two approaches. This makes it of course unusable to replace the C++0x code as is. I wondered if some #undef and #define trickery could work around this, allowing me to use enum class-like syntax (perhaps without the strict type safety etc.), but at least the same syntax. Thanks!

Community
  • 1
  • 1
rubenvb
  • 74,642
  • 33
  • 187
  • 332

3 Answers3

21

I just discovered a problem with James' good hack (which I have heretofore been using), and a fix to the problem. I discovered the problem when I tried to define a stream operator for my_enum.

#include <iostream>

struct my_enum {
    enum type { 
        value1, 
        value2 
    };

    my_enum(type v) : value_(v) { }

    operator type() const { return value_; }

private:

    type value_;
};

std::ostream&
operator<<(std::ostream& os, my_enum v)
{
    return os << "streaming my_enum";
}

int main()
{
    std::cout << my_enum::value1 << '\n';
}

The output is:

0

The problem is my_enum::value1 has different type than my_enum. Here's a hack to James' hack that I came up with.

struct my_enum
{
    static const my_enum value1;
    static const my_enum value2;

    explicit my_enum(int v) : value_(v) { }

    // explicit // if you have it!
       operator int() const { return value_; }

private:

    int value_;
};

my_enum const my_enum::value1(0);
my_enum const my_enum::value2(1);

Notes:

  1. Unless otherwise specified by an enum-base, the underlying type of a scoped enumeration is int.
  2. Explicit conversions to and from the underlying integral type are allowed. But implicit conversions are not. Do your best.
  3. This hack is more of a pita than James' because of the need to enumerate the values twice. I'm hoping compilers without scoped enum support rapidly become extinct!
Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
  • 1
    This approach does not seem to allow `switch`/`case`, at least on MSVC 10 (I get "C2051: case expression not constant"). This is a big PITA, since `enum`s are typically used this way. – Alexandre C. Feb 15 '12 at 10:56
  • @AlexandreC.: I think that could be worked around by initializing the faux enumerators when they are declared in the class definition instead of when they are defined outside of the class definition. – James McNellis May 23 '12 at 07:07
  • While this hack is quite good, it _is_ a big PITA as Alexandre says and looks near to impossible to make compatible to C++11 in a human-writeable way / without an extra preprocessor. Instead I use the TypeSafeEnum idiom from Wikibooks plus some inbuilt macros that disable some common `enum`operations (comparisons, `!`negation and streaming) via `enable_if`, the closest "humane" thing I've seen to disabling `enum` implicit conversion (which seems to be impossible). – Luis Machuca Aug 25 '12 at 17:10
5

Do not use this solution. See the accepted answer by Howard for a better solution. I'm leaving this post here because Howard's answer refers to it.

If you need to be able to compile your code with a compiler that doesn't yet support a new, not yet standard or not yet widely implemented language feature, it's best to avoid using that language feature in your code.

That said, as a hack workaround, you can wrap the enum in a struct and use a pair of implicit conversions:

struct my_enum {
    enum type { 
        value1, 
        value2 
    };

    my_enum(type v) : value_(v) { }

    operator type() const { return value_; }

private:

    type value_;
};
James McNellis
  • 348,265
  • 75
  • 913
  • 977
  • Is this a pattern? Why do you consider it a hack? It's neat. – Inverse Feb 12 '11 at 21:09
  • 1
    @Inverse: If you use it repeatedly it would become a pattern :-). Wrapping an enumeration in a struct or namespace to prevent namespace pollution is a common technique and one that I use consistently (well, mostly). Using implicit conversions to allow the encapsulating struct to be used as if it were the enumeration is not a common pattern, at least not in any code I've had the pleasure of reading. I'm wary of it because of the implication conversions: I'm continually discovering different ways that implicit conversions allow you to write subtly broken code... – James McNellis Feb 13 '11 at 15:14
0

I have been fighting for a whole day to find a truly optimal solution, but there doesn't seem to be one. I need my enum that is

  1. Not implicitly convertible to an integral type
  2. Usable in a switch statement
  3. Usable as non-type template parameter

In have come up with the following code, built upon Howard Hinnant's solution:

struct DataType
{
    struct integral {
        enum type { None, Single, Double, Int };
    };

    typedef typename integral::type integral_type;

    explicit DataType(integral_type v) : val(v) {}
    integral_type integral_value() const { return val; }

    bool operator==(const DataType& s) const { return val == s.val; }
    bool operator!=(const DataType& s) const { return val != s.val; }

    static const DataType None;
    static const DataType Single;
    static const DataType Double;
    static const DataType Int;

private:
    integral_type val;
};

In the .cpp file:

const DataType DataType::None   (DataType::integral::None);
const DataType DataType::Single (DataType::integral::Single);
const DataType DataType::Double (DataType::integral::Double);
const DataType DataType::Int    (DataType::integral::Int);

As non-type template parameter:

template <DataType::integral_type>
struct DataTypeTraits;

template <>
struct DataTypeTraits<DataType::integral::Single>
{
    enum { size = 4 };
};

In a switch:

size_t get_size(DataType type)
{
    switch (type.integral_value()) {
        case DataType::integral::Single:  return DataTypeTraits<DataType::integral::Single>::size;
        case DataType::integral::Double:  return DataTypeTraits<DataType::integral::Double>::size;
        case DataType::integral::Int:     return DataTypeTraits<DataType::integral::Int>::size;
        default:                          throw  std::logic_error("Unknown data type.");
    }
}

Not particularly great, but that's as good as it gets, I guess...

marton78
  • 3,899
  • 2
  • 27
  • 38