118

Are C++ enums signed or unsigned? And by extension is it safe to validate an input by checking that it is <= your max value, and leave out >= your min value (assuming you started at 0 and incremented by 1)?

Matt
  • 84,419
  • 25
  • 57
  • 67
  • When we are using an enum type in a context that requires the sign of it, we are actually talking about converting the enum to an integral type implicitly. The C++03 standard says this is done by Integral Promotion, nothing related to the underlying type of the enum. So, I don't understand why every answer here mentions the underlying type is not defined by the standard? I described the expected behaviou here: http://stackoverflow.com/questions/24802322/sign-of-c-enum-type-incorrect-after-converting-to-integral-type – JavaMan Jul 17 '14 at 12:10

10 Answers10

110

Let's go to the source. Here's what the C++03 standard (ISO/IEC 14882:2003) document says in 7.2-5 (Enumeration declarations):

The underlying type of an enumeration is an integral type that can represent all the enumerator values defined in the enumeration. It is implementation-defined which integral type is used as the underlying type for an enumeration except that the underlying type shall not be larger than int unless the value of an enumerator cannot fit in an int or unsigned int.

In short, your compiler gets to choose (obviously, if you have negative numbers for some of your ennumeration values, it'll be signed).

Michael Burr
  • 333,147
  • 50
  • 533
  • 760
  • How can we avoid compiler guesses and tell it to use an underlying unsigned type when all the enumeration values are small, positive integers? (We are catching a UBsan finding because the compiler is picking an int, and int's suffer overflow. The value is unsigned and positive, and our use depends on unsigned wrap to provide a decrement or "negative stride"). – jww Dec 16 '17 at 18:40
  • @jww - that'll depend on what compiler exactly you are using, I guess. Since the standard does not dictate the underlying type, and leaves this to the implementation, then need to look at your tool's documentation and see if this option is at all possible. If you want to guarantee a certain behaviour in your code, why not cast the enum member you use in the expression? – ysap Feb 09 '18 at 12:42
65

You shouldn't rely on any specific representation. Read the following link. Also, the standard says that it is implementation-defined which integral type is used as the underlying type for an enum, except that it shall not be larger than int, unless some value cannot fit into int or an unsigned int.

In short: you cannot rely on an enum being either signed or unsigned.

zvrba
  • 24,186
  • 3
  • 55
  • 65
  • 31
    [Michael Burr's answer](http://stackoverflow.com/a/159308/594137) (which quotes the standard) actually implies you *can* rely on it being signed if you define an enum value as negative due to the type being able to "represent all the enumerator values defined in the enumeration". – Samuel Harmer Dec 11 '12 at 08:20
26

You shouldn't depend on them being signed or unsigned. If you want to make them explicitly signed or unsigned, you can use the following:

enum X : signed int { ... };    // signed enum
enum Y : unsigned int { ... };  // unsigned enum
Adam Rosenfield
  • 390,455
  • 97
  • 512
  • 589
17

You shouldn't rely on it being either signed or unsigned. According to the standard it is implementation-defined which integral type is used as the underlying type for an enum. In most implementations, though, it is a signed integer.

In C++0x strongly typed enumerations will be added which will allow you to specify the type of an enum such as:

enum X : signed int { ... };    // signed enum
enum Y : unsigned int { ... };  // unsigned enum

Even now, though, some simple validation can be achieved by using the enum as a variable or parameter type like this:

enum Fruit { Apple, Banana };

enum Fruit fruitVariable = Banana;  // Okay, Banana is a member of the Fruit enum
fruitVariable = 1;  // Error, 1 is not a member of enum Fruit
                    // even though it has the same value as banana.
Alex Huck
  • 13
  • 4
Matt
  • 84,419
  • 25
  • 57
  • 67
8

Even some old answers got 44 upvotes, I tend to disagree with all of them. In short, I don't think we should care about the underlying type of the enum.

First off, C++03 Enum type is a distinct type of its own having no concept of sign. Since from C++03 standard dcl.enum

7.2 Enumeration declarations 
5 Each enumeration defines a type that is different from all other types....

So when we are talking about the sign of an enum type, say when comparing 2 enum operands using the < operator, we are actually talking about implicitly converting the enum type to some integral type. It is the sign of this integral type that matters. And when converting enum to integral type, this statement applies:

9 The value of an enumerator or an object of an enumeration type is converted to an integer by integral promotion (4.5).

And, apparently, the underlying type of the enum get nothing to do with the Integral Promotion. Since the standard defines Integral Promotion like this:

4.5 Integral promotions conv.prom
.. An rvalue of an enumeration type (7.2) can be converted to an rvalue of the first of the following types that can represent all the values of the enumeration
(i.e. the values in the range bmin to bmax as described in 7.2: int, unsigned int, long, or unsigned long.

So, whether an enum type becomes signed int or unsigned int depends on whether signed int can contain all the values of the defined enumerators, not the underlying type of the enum.

See my related question Sign of C++ Enum Type Incorrect After Converting to Integral Type

Community
  • 1
  • 1
JavaMan
  • 4,954
  • 4
  • 41
  • 69
  • It matters when you are compiling with `-Wsign-conversion`. We use it to help catch unintended mistakes in our code. But ***+1*** for citing the standard, and pointing out that an enum has no type (`signed` versus `unsigned`) associated with it. – jww Jul 06 '15 at 06:34
5

The compiler can decide whether or not enums are signed or unsigned.

Another method of validating enums is to use the enum itself as a variable type. For example:

enum Fruit
{
    Apple = 0,
    Banana,
    Pineapple,
    Orange,
    Kumquat
};

enum Fruit fruitVariable = Banana;  // Okay, Banana is a member of the Fruit enum
fruitVariable = 1;  // Error, 1 is not a member of enum Fruit even though it has the same value as banana.
Cristián Romo
  • 9,814
  • 12
  • 50
  • 50
5

In the future, with C++0x, strongly typed enumerations will be available and have several advantages (such as type-safety, explicit underlying types, or explicit scoping). With that you could be better assured of the sign of the type.

Kris Kumler
  • 6,307
  • 3
  • 24
  • 27
4

In addition to what others have already said about signed/unsigned, here's what the standard says about the range of an enumerated type:

7.2(6): "For an enumeration where e(min) is the smallest enumerator and e(max) is the largest, the values of the enumeration are the values of the underlying type in the range b(min) to b(max), where b(min) and b(max) are, respectively, the smallest and largest values of the smallest bitfield that can store e(min) and e(max). It is possible to define an enumeration that has values not defined by any of its enumerators."

So for example:

enum { A = 1, B = 4};

defines an enumerated type where e(min) is 1 and e(max) is 4. If the underlying type is signed int, then the smallest required bitfield has 4 bits, and if ints in your implementation are two's complement then the valid range of the enum is -8 to 7. If the underlying type is unsigned, then it has 3 bits and the range is 0 to 7. Check your compiler documentation if you care (for example if you want to cast integral values other than enumerators to the enumerated type, then you need to know whether the value is in the range of the enumeration or not - if not the resulting enum value is unspecified).

Whether those values are valid input to your function may be a different issue from whether they are valid values of the enumerated type. Your checking code is probably worried about the former rather than the latter, and so in this example should at least be checking >=A and <=B.

Steve Jessop
  • 273,490
  • 39
  • 460
  • 699
4

Check it with std::is_signed<std::underlying_type + scoped enums default to int

https://en.cppreference.com/w/cpp/language/enum implies:

main.cpp

#include <cassert>
#include <iostream>
#include <type_traits>

enum Unscoped {};
enum class ScopedDefault {};
enum class ScopedExplicit : long {};

int main() {
    // Implementation defined, let's find out.
    std::cout << std::is_signed<std::underlying_type<Unscoped>>() << std::endl;

    // Guaranteed. Scoped defaults to int.
    assert((std::is_same<std::underlying_type<ScopedDefault>::type, int>()));

    // Guaranteed. We set it ourselves.
    assert((std::is_same<std::underlying_type<ScopedExplicit>::type, long>()));
}

GitHub upstream.

Compile and run:

g++ -std=c++17 -Wall -Wextra -pedantic-errors -o main main.cpp
./main

Output:

0

Tested on Ubuntu 16.04, GCC 6.4.0.

Ciro Santilli OurBigBook.com
  • 347,512
  • 102
  • 1,199
  • 985
-1

While some of the above answers are arguably proper, they did not answer my practical question. The compiler (gcc 9.3.0) emitted warnings for:

enum FOO_STATUS {
    STATUS_ERROR = (1 << 31)
};

The warning was issued on use:

unsigned status = foo_status_get();
if (STATUS_ERROR == status) {

(Aside from the fact this code is incorrect ... do not ask.)

When asked properly, the compiler does not emit an error.

enum FOO_STATUS {
    STATUS_ERROR = (1U << 31)
};

Note that 1U makes the expression unsigned.