Are there any guarantees on the representation of large enum values? - c++

Suppose I have (on a 32 bit machine)
enum foo {
val1 = 0x7FFFFFFF, // originally '2^31 - 1'
val2,
val3 = 0xFFFFFFFF, // originally '2^32 - 1'
val4,
val5
};
what is the value of val2, val4 and val5? I know I could test it, but is the result standardized?

In C standard:
C11 (n1570), § 6.7.2.2 Enumeration specifiers
Each enumerated type shall be compatible with char, a signed integer type, or an unsigned integer type. The choice of type is implementation-defined, but shall be capable of representing the values of all the members of the enumeration.
If the underlying type used by the compiler is not capable to represent these values, the behavior is undefined.
C11 (n1570), § 4. Conformance
If a ‘‘shall’’ or ‘‘shall not’’ requirement that appears outside of a constraint or runtime-constraint is violated, the behavior is undefined.

From the C++11 standard (§7.2,6, emphasis mine):
For an enumeration whose underlying type is not fixed, the underlying type is an integral type that can represent all the enumerator values defined in the enumeration. If no integral type can represent all the enumerator values, the enumeration is ill-formed. It is implementation-defined which integral type is used as the underlying type 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.
So the compiler will happily do The Right Thing if there is an integral type bigger than 32bit. If not, the enum is illformed. There will be no wrapping around.
The values will be:
enum foo {
val1 = 0x7FFFFFFF,
val2, // 0x80000000 = 2^31
val3 = 0xFFFFFFFF,
val4, //0x0000000100000000 = 2^32
val5 //0x0000000100000001 = 2^32+1
};
The increasing numbers are well defined as well (§7.2,2):
[...] An enumerator-definition without an initializer gives the enumerator the value obtained by increasing the value of the previous enumerator by one.

C99 / C11
Prelude:
5.2.4.2.1 requires int to be at least 16 bits wide; AFAIK there's no upper bound (long must be longer or equal, though, 6.2.5 /8).
6.5 /5:
If an exceptional condition occurs during the evaluation of an expression (that is, if the result is not mathematically defined or not in the range of representable values for its type), the behavior is undefined.
If your `int` is 32 bits wide (or less)
then the example in the OP is a violation of constraint 6.7.2.2 /2:
The expression that defines the value of an enumeration constant shall be an integer
constant expression that has a value representable as an int.
Furthermore, the enumerators are defined as constant of type int, 6.7.2.2 /3:
The identifiers in an enumerator list are declared as constants that have type int and
may appear wherever such are permitted.
Note, there's a difference between the type of the enumeration and the type of an enumerator / enumeration constant:
enum foo { val0 };
enum foo myVariable; // myVariable has the type of the enumeration
uint_least8_t v = val0*'c'; // if val0 appears in any expression, it has type int
It seems to me this allows narrowing, e.g. reducing the size of the enum type to 8 bits:
enum foo { val1 = 1, val2 = 5 };
enum foo myVariable = val1; // allowed to be 8-bit
But it seems to disallow widening, e.g.
enum foo { val1 = INT_MAX+1 }; // constraint violation AND undefined behaviour
// not sure about the following, we're already in UB-land
enum foo myVariable = val1; // maximum value of an enumerator still is INT_MAX
// therefore myVariable will have sizeof int
Auto-increment of enumerators
Because of 6.7.2.2 /3,
[...] Each subsequent enumerator with no = defines its enumeration constant as the value of the constant expression obtained by adding 1 to the value of the previous enumeration constant. [...]
the example results in UB:
enum foo {
val0 = INT_MAX,
val1 // equivalent to `val1 = INT_MAX+1`
};

Here's the C++ answer: in 7.2/6, it states:
[...] the underlying type is an integral type that can represent all
the enumerator values defined in the enumeration. If no integral type
can represent all the enumerator values, the enumeration is
ill-formed. It is implementation-defined which integral type is used
as the underlying type 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.
So compared to C: no undefined behavior if the compiler can't find a type, and the compiler can't just use its 512-bit extended integer type for your two-value enum.
Which means that in your example, the underlying type will probably be some signed 64-bit type - most compilers always try the signed version of a type first.

Related

Does enumerator used in expression have the same type as underlying type of its enum?

What is the type of enumeration constant, when it is used outside unscoped enum definition?
Consider following code:
#include <iostream>
enum modes
{
begin = 0,
end = 1
};
int main()
{
std::cout << std::boolalpha
<< std::is_same<unsigned int, typename std::underlying_type<modes>::type>::value
<< std::endl;
std::cout << sizeof(modes) << std::endl;
std::cout << (-100 + end) << std::endl;
}
This yields on my machine:
true
4
-99
Now, if I only change the value of some other enumerator, begin to 2147483648, then my output becomes:
true
4
4294967197
Apparently, It means, that type of end has changed from int to unsigned int, even underlying type of modes is still the same (i.e. unsigned int).
Are there some special rules for integral promotions regarding enums?
From the C++ Standard (7.2 Enumeration declarations)
...Following the closing brace of an enum-specifier, each enumerator has the type of its enumeration....
And (4.5 Integral promotions)
3 A prvalue of an unscoped enumeration type whose underlying type is
not fixed (7.2) can be converted to a prvalue 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 int, unsigned long int, long long int, or unsigned
long long int. If none of the types in that list can represent all the
values of the enumeration, a prvalue of an unscoped enumeration type
can be converted to a prvalue of the extended integer type with lowest
integer conversion rank (4.13) greater than the rank of long long in
which all the values of the enumeration can be represented. If there
are two such extended types, the signed one is chosen.
What is the type of enumeration constant, when it is used outside unscoped enum definition?
It's the enumeration type, of course. The type of begin is modes. If it had an integral type, your question regarding integral promotions of enums wouldn't make sense.
Are there some special rules for integral promotions with enums?
Not really. You're thinking about integral promotions of the enum's underlying type, but that's something different. For the enum type itself, you need to think about the range of valid values of the enum type, which can be smaller than the range of valid values of the enum's underlying type. Can int can represent all valid values? If so, you get an int out of it.

Is it allowed for an enum to have an unlisted value? [duplicate]

This question already has an answer here:
What happens if you static_cast invalid value to enum class?
(1 answer)
Closed 7 years ago.
Say, we have
enum E
{
Foo = 0,
Bar = 1
};
Now, we do
enum E v = ( enum E ) 2;
And then
switch ( v )
{
case Foo:
doFoo();
break;
case Bar:
doBar();
break;
default:
// Is the compiler required to honor this?
doOther();
break;
}
Since the switch above handles every possible listed value of the enum, is it allowed for the compiler to optimize away the default branch above, or otherwise have an unspecified or undefined behavior in the case the value of enum is not in the list?
As I am expecting that the behavior should be similar for C and C++, the question is about both languages. However, if there's a difference between C and C++ for that case, it would be nice to know about it, too.
C++ situation
In C++, each enum has an underlying integral type. It can be fixed, if it is explicitly specified (ex: enum test2 : long { a,b};) or if it is int by default in the case of a scoped enum (ex: enum class test { a,b };):
[dcl.enum]/5: Each enumeration defines a type that is different from all other types. Each enumeration also has an underlying type. (...) if not
explicitly specified, the underlying type of a scoped enumeration type
is int. In these cases, the underlying type is said to be fixed.
In the case of an unscoped enum where the underlying type was not explicitely fixed (your example), the standard gives more flexibility to your compiler:
[dcl.enum]/7: For an enumeration whose underlying type is not fixed, the underlying type 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 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.
Now a very tricky thing: the values that can be held by an enum variable depends on whether or not the underlying type is fixed:
if it's fixed, "the values of the enumeration are the values of the
underlying type."
otherwhise, it is the integral values within the minimum and the maximum of the smallest bit-field that can hold the smallest enumerator and the largest one.
You are in the second case, although your code will work on most compilers, the smallest bitfield has a size of 1 and so the only values that you can for sure hold on all compliant C++ compilers are those between 0 and 1...
Conclusion: If you want to ensure that the value can be set to 2, you either have to make your enum a scoped enum, or explicitly indicate an underlying type.**
More reading:
SO question on how to check if an enum value is valid
article on avoiding enum out-of-rang in secure coding.
Stroutstrup's plaidoyer for scoped enum over unscoped ones
C situation
The C situation is much simpler (C11):
6.2.5/16: An enumeration comprises a set of named integer constant values. Each distinct enumeration constitutes a different enumerated
type.
So basically, it is an int:
6.7.2.2./2 The expression that defines the value of an enumeration constant shall be an integer constant expression that has a value
representable as an int.
With the following restriction:
Each enumerated type shall be compatible with char, a signed integer
type, or an unsigned integer type. The choice of type is
implementation-defined, but shall be capable of representing the
values of all the members of the enumeration.
In C an enum type is an integer type large enough to hold all the enum constants:
(C11, 6.7.2.2p4) "Each enumerated type shall be compatible with char, a signed integer type, or an unsigned integer type. The choice of type is implementation-defined,110) but shall be capable of representing the values of all the members of the enumeration".
Let's say the selected type for enum E is _Bool. A _Bool object can only store the values 0 and 1. It's not possible to have a _Bool object storing a value different than 0 or 1 without invoking undefined behavior.
In that case the compiler is allowed to assume that an object of the enum E type can only hold 0 or 1 in a strictly conforming program and is so allowed to optimize out the default switch case.
C++Std 7.2.7 [dcl.enum]:
It is possible to define an enumeration that has values not defined by any of its enumerators.
So, you can have enumeration values which are not listed in enumerator list.
But in your specific case, the 'underlying type' is not 'fixed' (7.2.5). The specification doesn't say which is the underlying type in that case, but it must be integral. Since char is the smallest such type, we can conclude that there are other values of the enum which are not specified in the enumerator list.
Btw, I think that the compiler can optimize your case when it can determine that there are no other values ever assigned to v, which is safe, but I think there are no compilers which are that smart yet.
Also, 7.2/10:
An expression of arithmetic or enumeration type can be converted to an
enumeration type explicitly. The value is unchanged if it is in the
range of enumeration values of the enumeration type; otherwise the
resulting enumeration value is unspecified.
In C enumerators have type int . Thus any integer value can be assigned to an object of the enumeration type.
From the C Standard (6.7.2.2 Enumeration specifiers)
3 The identifiers in an enumerator list are declared as constants that
have type int and may appear wherever such are permitted.
In C++ enumerators have type of the enumeration that defines it. In C++ you should either expliicitly to specify the underlaying type or the compiler calculates itself the maximum allowed value.
From the C++ Standard (7.2 Enumeration declarations)
5 Each enumeration defines a type that is different from all other types. Each enumeration also has an underlying type. The underlying type can be explicitly specified using enum-base; if not explicitly specified, the underlying type of a scoped enumeration type is int. In these cases, the underlying type is said to be fixed. Following the closing brace of an enum-specifier, each enumerator has the type of its enumeration.
Thus in C any possible value of a enum is any integer value. The compiler may not optimize a switch removing the default label.
In C and C++, this can work.
Same code for both:
#include <stdio.h>
enum E
{
Foo = 0,
Bar = 1
};
int main()
{
enum E v = (enum E)2; // the cast is required for C++, but not for C
printf("v = %d\n", v);
switch (v) {
case Foo:
printf("got foo\n");
break;
case Bar:
printf("got bar\n");
break;
default:
printf("got \n", v);
break;
}
}
Same output for both:
v = 2
got default
In C, an enum is an integral type, so you can assign an integer value to it without casting. In C++, an enum is its own type.

Converting int to C++11 scoped enum [duplicate]

Consider this C++ code:
enum class Color : char { red = 0x1, yellow = 0x2 }
// ...
char *data = ReadFile();
Color color = static_cast<Color>(data[0]);
Suppose that data[0] is actually 100. What is color set to according to the standard?
In particular, if I later do
switch (color) {
// ... red and yellow cases omitted
default:
// handle error
break;
}
does the standard guarantee that default will be hit? If not, what is the proper, most efficient, most elegant way to check for an error here? Does the standard make any guarantees as about this but with plain enum?
What is color set to according to the standard?
Answering with a quote from the C++11 and C++14 Standards:
[expr.static.cast]/10
A value of integral or enumeration type can be explicitly converted to an enumeration type. The value is unchanged if the original value is within the range of the enumeration values (7.2). Otherwise, the resulting value is unspecified (and might not be in that range).
Let's look up the range of the enumeration values: [dcl.enum]/7
For an enumeration whose underlying type is fixed, the values of the enumeration are the values of the underlying type.
Before CWG 1766 (C++11, C++14)
Therefore, for data[0] == 100, the resulting value is specified(*), and no Undefined Behaviour (UB) is involved. More generally, as you cast from the underlying type to the enumeration type, no value in data[0] can lead to UB for the static_cast.
After CWG 1766 (C++17)
See CWG defect 1766.
The [expr.static.cast]p10 paragraph has been strengthened, so you now can invoke UB if you cast a value that is outside the representable range of an enum to the enum type. This still doesn't apply to the scenario in the question, since data[0] is of the underlying type of the enumeration (see above).
Please note that CWG 1766 is considered a defect in the Standard, hence it is accepted for compiler implementers to apply to to their C++11 and C++14 compilation modes.
(*) char is required to be at least 8 bit wide, but isn't required to be unsigned. The maximum value storable is required to be at least 127 per Annex E of the C99 Standard.
Compare to [expr]/4
If during the evaluation of an expression, the result is not mathematically defined or not in the range of representable values for its type, the behavior is undefined.
Before CWG 1766, the conversion integral type -> enumeration type can produce an unspecified value. The question is: Can an unspecified value be outside the representable values for its type? I believe the answer is no -- if the answer was yes, there wouldn't be any difference in the guarantees you get for operations on signed types between "this operation produces an unspecified value" and "this operation has undefined behaviour".
Hence, prior to CWG 1766, even static_cast<Color>(10000) would not invoke UB; but after CWG 1766, it does invoke UB.
Now, the switch statement:
[stmt.switch]/2
The condition shall be of integral type, enumeration type, or class type. [...] Integral promotions are performed.
[conv.prom]/4
A prvalue of an unscoped enumeration type whose underlying type is fixed (7.2) can be converted to a prvalue of its underlying type. Moreover, if integral promotion can be applied to its underlying type, a prvalue of an unscoped enumeration type whose underlying type is fixed can also be converted to a prvalue of the promoted underlying type.
Note: The underlying type of a scoped enum w/o enum-base is int. For unscoped enums the underlying type is implementation-defined, but shall not be larger than int if int can contain the values of all enumerators.
For an unscoped enumeration, this leads us to /1
A prvalue of an integer type other than bool, char16_t, char32_t, or wchar_t whose integer conversion rank (4.13) is less than the rank of int can be converted to a prvalue of type int if int can represent all the values of the source type; otherwise, the source prvalue can be converted to a prvalue of type unsigned int.
In the case of an unscoped enumeration, we would be dealing with ints here. For scoped enumerations (enum class and enum struct), no integral promotion applies. In any way, the integral promotion doesn't lead to UB either, as the stored value is in the range of the underlying type and in the range of int.
[stmt.switch]/5
When the switch statement is executed, its condition is evaluated and compared with each case constant. If one of the case constants is equal to the value of the condition, control is passed to the statement following the matched case label. If no case constant matches the condition, and if there is a default label, control passes to the statement labeled by the default label.
The default label should be hit.
Note: One could take another look at the comparison operator, but it is not explicitly used in the referred "comparison". In fact, there's no hint it would introduce UB for scoped or unscoped enums in our case.
As a bonus, does the standard make any guarantees as about this but with plain enum?
Whether or not the enum is scoped doesn't make any difference here. However, it does make a difference whether or not the underlying type is fixed. The complete [decl.enum]/7 is:
For an enumeration whose underlying type is fixed, the values of the enumeration are the values of the underlying type. Otherwise, for an enumeration where emin is the smallest enumerator and emax is the largest, the values of the enumeration are the values in the range bmin to bmax, defined as follows: Let K be 1 for a two's complement representation and 0 for a one's complement or sign-magnitude representation. bmax is the smallest value greater than or equal to max(|emin| − K, |emax|) and equal to 2M − 1, where M is a non-negative integer. bmin is zero if emin is non-negative and −(bmax + K) otherwise.
Let's have a look at the following enumeration:
enum ColorUnfixed /* no fixed underlying type */
{
red = 0x1,
yellow = 0x2
}
Note that we cannot define this as a scoped enum, since all scoped enums have fixed underlying types.
Fortunately, ColorUnfixed's smallest enumerator is red = 0x1, so max(|emin| − K, |emax|) is equal to |emax| in any case, which is yellow = 0x2. The smallest value greater or equal to 2, which is equal to 2M - 1 for a positive integer M is 3 (22 - 1). (I think the intent is to allow the range to extent in 1-bit-steps.) It follows that bmax is 3 and bmin is 0.
Therefore, 100 would be outside the range of ColorUnfixed, and the static_cast would produce an unspecified value before CWG 1766 and undefined behaviour after CWG 1766.

What happens if you static_cast invalid value to enum class?

Consider this C++ code:
enum class Color : char { red = 0x1, yellow = 0x2 }
// ...
char *data = ReadFile();
Color color = static_cast<Color>(data[0]);
Suppose that data[0] is actually 100. What is color set to according to the standard?
In particular, if I later do
switch (color) {
// ... red and yellow cases omitted
default:
// handle error
break;
}
does the standard guarantee that default will be hit? If not, what is the proper, most efficient, most elegant way to check for an error here? Does the standard make any guarantees as about this but with plain enum?
What is color set to according to the standard?
Answering with a quote from the C++11 and C++14 Standards:
[expr.static.cast]/10
A value of integral or enumeration type can be explicitly converted to an enumeration type. The value is unchanged if the original value is within the range of the enumeration values (7.2). Otherwise, the resulting value is unspecified (and might not be in that range).
Let's look up the range of the enumeration values: [dcl.enum]/7
For an enumeration whose underlying type is fixed, the values of the enumeration are the values of the underlying type.
Before CWG 1766 (C++11, C++14)
Therefore, for data[0] == 100, the resulting value is specified(*), and no Undefined Behaviour (UB) is involved. More generally, as you cast from the underlying type to the enumeration type, no value in data[0] can lead to UB for the static_cast.
After CWG 1766 (C++17)
See CWG defect 1766.
The [expr.static.cast]p10 paragraph has been strengthened, so you now can invoke UB if you cast a value that is outside the representable range of an enum to the enum type. This still doesn't apply to the scenario in the question, since data[0] is of the underlying type of the enumeration (see above).
Please note that CWG 1766 is considered a defect in the Standard, hence it is accepted for compiler implementers to apply to to their C++11 and C++14 compilation modes.
(*) char is required to be at least 8 bit wide, but isn't required to be unsigned. The maximum value storable is required to be at least 127 per Annex E of the C99 Standard.
Compare to [expr]/4
If during the evaluation of an expression, the result is not mathematically defined or not in the range of representable values for its type, the behavior is undefined.
Before CWG 1766, the conversion integral type -> enumeration type can produce an unspecified value. The question is: Can an unspecified value be outside the representable values for its type? I believe the answer is no -- if the answer was yes, there wouldn't be any difference in the guarantees you get for operations on signed types between "this operation produces an unspecified value" and "this operation has undefined behaviour".
Hence, prior to CWG 1766, even static_cast<Color>(10000) would not invoke UB; but after CWG 1766, it does invoke UB.
Now, the switch statement:
[stmt.switch]/2
The condition shall be of integral type, enumeration type, or class type. [...] Integral promotions are performed.
[conv.prom]/4
A prvalue of an unscoped enumeration type whose underlying type is fixed (7.2) can be converted to a prvalue of its underlying type. Moreover, if integral promotion can be applied to its underlying type, a prvalue of an unscoped enumeration type whose underlying type is fixed can also be converted to a prvalue of the promoted underlying type.
Note: The underlying type of a scoped enum w/o enum-base is int. For unscoped enums the underlying type is implementation-defined, but shall not be larger than int if int can contain the values of all enumerators.
For an unscoped enumeration, this leads us to /1
A prvalue of an integer type other than bool, char16_t, char32_t, or wchar_t whose integer conversion rank (4.13) is less than the rank of int can be converted to a prvalue of type int if int can represent all the values of the source type; otherwise, the source prvalue can be converted to a prvalue of type unsigned int.
In the case of an unscoped enumeration, we would be dealing with ints here. For scoped enumerations (enum class and enum struct), no integral promotion applies. In any way, the integral promotion doesn't lead to UB either, as the stored value is in the range of the underlying type and in the range of int.
[stmt.switch]/5
When the switch statement is executed, its condition is evaluated and compared with each case constant. If one of the case constants is equal to the value of the condition, control is passed to the statement following the matched case label. If no case constant matches the condition, and if there is a default label, control passes to the statement labeled by the default label.
The default label should be hit.
Note: One could take another look at the comparison operator, but it is not explicitly used in the referred "comparison". In fact, there's no hint it would introduce UB for scoped or unscoped enums in our case.
As a bonus, does the standard make any guarantees as about this but with plain enum?
Whether or not the enum is scoped doesn't make any difference here. However, it does make a difference whether or not the underlying type is fixed. The complete [decl.enum]/7 is:
For an enumeration whose underlying type is fixed, the values of the enumeration are the values of the underlying type. Otherwise, for an enumeration where emin is the smallest enumerator and emax is the largest, the values of the enumeration are the values in the range bmin to bmax, defined as follows: Let K be 1 for a two's complement representation and 0 for a one's complement or sign-magnitude representation. bmax is the smallest value greater than or equal to max(|emin| − K, |emax|) and equal to 2M − 1, where M is a non-negative integer. bmin is zero if emin is non-negative and −(bmax + K) otherwise.
Let's have a look at the following enumeration:
enum ColorUnfixed /* no fixed underlying type */
{
red = 0x1,
yellow = 0x2
}
Note that we cannot define this as a scoped enum, since all scoped enums have fixed underlying types.
Fortunately, ColorUnfixed's smallest enumerator is red = 0x1, so max(|emin| − K, |emax|) is equal to |emax| in any case, which is yellow = 0x2. The smallest value greater or equal to 2, which is equal to 2M - 1 for a positive integer M is 3 (22 - 1). (I think the intent is to allow the range to extent in 1-bit-steps.) It follows that bmax is 3 and bmin is 0.
Therefore, 100 would be outside the range of ColorUnfixed, and the static_cast would produce an unspecified value before CWG 1766 and undefined behaviour after CWG 1766.

Are C++ enums signed or unsigned?

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)?
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).
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.
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
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.
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
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.
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.
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.
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.
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.