4

I've looked all over the place and I can't believe this question has not been asked before.

Is the ordering of scoped enumerators defined by the standard? Say if I have the following

#include <iostream>

enum class Fruits {Apple, Orange, Tomato};

int main(){

   std::cout << (Fruits::Apple < Fruits::Orange);
   std::cout << (Fruits::Orange > Fruits::Tomato);
   return 0;
}


// output:
// 1 0

This outputs 1 0 in g++. But is this standard or compiler specific?

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
GamefanA
  • 1,555
  • 2
  • 16
  • 23
  • Almost certain it is standard defined for each unspecified value to be an increment of the previous and starting with 0. – user975989 May 19 '19 at 05:28
  • Are you asking about what order it should produce or about whether or not this should even be a valid expression to compare two scoped enumerators? – StoryTeller - Unslander Monica May 19 '19 at 05:34
  • "what order it should produce." – GamefanA May 19 '19 at 05:40
  • Values of integer, floating-point, and enumeration types can be converted by static_cast or explicit cast, to any enumeration type. If the underlying type is not fixed, the result is unspecified (until C++17)undefined behavior (since C++17) https://en.cppreference.com/w/cpp/language/enum It looks like while it's unspecified, it generally works. – doug May 19 '19 at 05:45

2 Answers2

4

To complete the picture and confirm that yes, the ordering is defined. We start with why one may put two enumerators in a relational expression...

[expr.rel]

2 The usual arithmetic conversions are performed on operands of arithmetic or enumeration type...

5 If both operands (after conversions) are of arithmetic or enumeration type, each of the operators shall yield true if the specified relationship is true and false if it is false.

... where the usual arithmetic conversion for a scoped enumeration is a no-op...

[expr]

9 Many binary operators that expect operands of arithmetic or enumeration type cause conversions and yield result types in a similar way. The purpose is to yield a common type, which is also the type of the result. This pattern is called the usual arithmetic conversions, which are defined as follows:

  • If either operand is of scoped enumeration type ([dcl.enum]), no conversions are performed; if the other operand does not have the same type, the expression is ill-formed.

So they do not convert, and may only be compared to an object of the same exact type. The values given to the enumerators (as specified by the other answer) are what determines if "each of the operators shall yield true if the specified relationship is true and false if it is false". That's how the comparison is done.

It's also worth noting that enumeration type variables can take values that are not enumerated! So for instance...

enum class foo {
  min, max = 10
};

foo mid = static_cast<foo>(5);

... is valid, and the comparison mid > foo::min will hold just the same, because the preceding paragraph is generalized to include more than just the named values.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
2

The ordering of the values is specified in [dcl.enum] point 2:

If the first enumerator has no initializer, the value of the corresponding constant is zero. An enumerator-definition without an initializer gives the enumerator the value obtained by increasing the value of the previous enumerator by one.

Therefore the values of Fruits are 0, 1, 2 respectively and since comparisons on enums are little more than type safe integer operations it behaves as you see.

user975989
  • 2,578
  • 1
  • 20
  • 38
  • I knew that was the case for unscoped enums, but I wasn't sure that the standard guarantees the same for scoped enums as well. Thanks for the reference. – GamefanA May 19 '19 at 05:41