0

The lack of implicit conversion can be painful when using scoped enums for bitwise flags:

enum class Flags: uint32_t { Foo = 1, Bar = 2 };

uint32_t foobar = Flags::Foo | Flags::Bar; // Error

I would tend to allow some implicit conversion with global operators such as:

template< typename T, typename R = typename std::underlying_type< T >::type, typename = typename std::enable_if< std::is_enum< T >::value && std::is_unsigned< R >::value >::type >
inline R operator |( T v1, T v2 )
{
    return static_cast< R >( v1 ) | static_cast< R >( v2 );
}

template< typename T, typename R = typename std::underlying_type< T >::type, typename = typename std::enable_if< std::is_enum< T >::value && std::is_integral< R >::value >::type >
inline bool operator ==( R u, T v )
{
    return u == static_cast< R >( v );
}

In which situation can these operators cause an issue or bug?

In the C++ FAQs we can read about scoped and strongly typed enums:

Conventional enums implicitly convert to int, causing errors when someone does not want an enumeration to act as an integer.

With the following example:

enum class Color { red, blue };

int i = Color::blue; // error: not Color->int conversion

I'm struggling to find an error scenario with such an implicit conversion to the underlying type, if the enum is an integral.

The need of static_cast may IMHO also be dangerous, as it may silence a truncation:

enum class Color: uint64_t { red, blue = std::numeric_limits< uint64_t >::max() };

uint32_t i = static_cast< uint32_t >( Color::blue ); // No error, but invalid value

Of course, we should instead cast to the proper underlying type:

uint32_t i = static_cast< std::underlying_type< Color >::type >( Color::blue ); // OK, error here

But this is so ugly I doubt I'll be able to convince anyone in my team to use that...

Edit

Based on the (many) comments, here are some details.

I'm not suggesting implicit conversion should be added to C++.
I'm asking about the potential issues of introducing some global operators to help with that in an existing code base.

I would like everybody in my team to use scoped enums - But hard to convince them if they need to use static_casts every time they have some bitwise flags, hence me trying to allow some implicit operations.

Macmade
  • 52,708
  • 13
  • 106
  • 123
  • not sure if I understand the question. Just consider any function that expects an `int` but accidentally gets passed an enum. Thats an error that absence of implicit casts prevents – 463035818_is_not_an_ai Mar 26 '20 at 16:15
  • Why would this be a problem if the enum's underlying type is `int`? – Macmade Mar 26 '20 at 16:16
  • 1
    the premise is that it happened by accident and that it is a problem. With implicit casts no way the compiler can detect such a mistake – 463035818_is_not_an_ai Mar 26 '20 at 16:17
  • [This answer](https://stackoverflow.com/a/18335862/4342498) has a good example. – NathanOliver Mar 26 '20 at 16:17
  • ^^ yes that would have been my next example, you can treat apples as oranges, when maybe apples arent really oranges – 463035818_is_not_an_ai Mar 26 '20 at 16:18
  • *The need of static_cast may IMHO also be dangerous* Sure, you can get bad behavior with static_cast, but that is you doing it, not the compiler. Garbage in, garbage out as they say. – NathanOliver Mar 26 '20 at 16:18
  • @NathanOliver I saw this answer, but it doesn't really add anything nor provide am example where implicit conversion may be dangerous. – Macmade Mar 26 '20 at 16:20
  • 2
    @Macmade The `if (color == Card::red_card) // no problem (bad) cout << "bad" << endl; if (card == Color::green) // no problem (bad) cout << "bad" << endl;` code doesn't show you why the implicit conversion is a bad thing? – NathanOliver Mar 26 '20 at 16:21
  • if you think it is fine to mix objects of different type without any safety net then you dont care about typesafety, but then I really dont understand your question – 463035818_is_not_an_ai Mar 26 '20 at 16:21
  • @idclev463035818 Maybe consider bitwise flags? - see the `|` operator in the post – Macmade Mar 26 '20 at 16:24
  • @NathanOliver Sure, but you can do exactly the same mistake using `static_cast`. – Macmade Mar 26 '20 at 16:27
  • not all enums are used in this way. Of course if you explicitly ask for you can get the underlying type, that doesnt mean that it is always dersirable to get that conversion implicitly – 463035818_is_not_an_ai Mar 26 '20 at 16:27
  • you know that you can still use plain old enums (wrapped inside a struct or namespace to get them scoped), right? – 463035818_is_not_an_ai Mar 26 '20 at 16:29
  • @idclev463035818 I understand the choice not to do this in C++. Question is about the potential issues introducing some operators in a codebase. – Macmade Mar 26 '20 at 16:29
  • @Macmade Sure, but ff you are using `static_cast`, then you are taking over control the get the code the compile. You really shouldn't be using `static_cast` with a enum class as you are subverting what it is designed to do. – NathanOliver Mar 26 '20 at 16:29
  • @idclev463035818 Yes, but would prefer to avoid that, – Macmade Mar 26 '20 at 16:30
  • @NathanOliver So again, what about bitwise flags? – Macmade Mar 26 '20 at 16:30
  • sorry (hope there is no sense of agression here, because thats not my intention at all), but if you consider the `Color::green == Card::red_card`to be fine, then I am afraid there is no example that you consider as "dangerous" because thats all the point about not having implicit conversions, if you are fine with them then go with it – 463035818_is_not_an_ai Mar 26 '20 at 16:32
  • 1
    @Macmade Then you add an overloaded operator like you did. That keeps the code safe because you are taking in the same enum type on both sides, unlike `if (color == Card::red_card)` which allows different enums because of the implicit cast. – NathanOliver Mar 26 '20 at 16:34
  • 1
    sorry misread your `==` to allow two different enum types, when in fact it is only about comparing enum to its underlying type, i see no problem with that – 463035818_is_not_an_ai Mar 26 '20 at 16:35
  • @idclev463035818 No problem : ) – Macmade Mar 26 '20 at 16:36
  • @idclev463035818 And obviously, I don't consider `Color::green == Card::red_card` to be fine. That was not my goal. : ) – Macmade Mar 26 '20 at 16:40
  • 1
    after all i think I understood your question and it roughly boils down to "we now have two extremes: old enums with close to no typesafety and scoped enums that are sometimes too restrictive, so what if you need something in between?". I'd say, go for it, but carefully choose what to enable and what not. With scoped enums now you can choose what you want to allow and that is a good thing – 463035818_is_not_an_ai Mar 26 '20 at 16:42
  • @idclev463035818 Thanks - That's exactly the question. Maybe I should have phrased it differently. – Macmade Mar 26 '20 at 16:46
  • 1
    I am sometimes a bit slow ;) glad if it helped in any way – 463035818_is_not_an_ai Mar 26 '20 at 16:53

1 Answers1

0

Consider this:

enum State { ON, OFF }; // Old style enum

class Light
{
public:
    void turnOn(bool on)
    {
        cout << boolalpha << "On is " << on;
    }
};

void setLightState(Light& light, State state)
{
    light.turnOn(state); // State accidentally converted to in here
}

int main()
{
    Light l;

    setLightState(l, ON); // Outputs 'On is false'
}

In the line marked, state is implicitly converted to an int, which is acceptable to pass to a bool. But the logic is inverted, since ON has an int value of 0, and OFF is 1. An enum class would give a compile-time error.

Jasper Kent
  • 3,546
  • 15
  • 21
  • Thanks for the answer, but that's not what I'm suggesting to allow. Maybe read the question again? I'm talking about allowing some operations with the underlying type. The underlying type of your `State` enum is not `bool`. – Macmade Mar 26 '20 at 16:54
  • 'I'm struggling to find an error scenario with an implicit conversion to the underlying type, if the enum is an integral.' This is an error scenario caused by an implicit conversion to the underlying type. The underlying type is int. The int then get converted to a bool. – Jasper Kent Mar 26 '20 at 16:59
  • Some compilers also warn on int to bool conversion. So that would not happen in my situation. – Macmade Mar 26 '20 at 17:15