Is this warning related to enum class size wrong? - c++

Warning:
src/BoardRep.h:49:12: warning: ‘BoardRep::BoardRep::Row::<anonymous struct>::a’
is too small to hold all values of ‘enum class BoardRep::Piece’
[enabled by default]
Piece a:2;
^
Enum:
enum class Piece: unsigned char {
EMPTY,
WHITE,
BLACK
};
Use:
union Row {
struct {
Piece a:2;
Piece b:2;
Piece c:2;
Piece d:2;
Piece e:2;
Piece f:2;
Piece g:2;
Piece h:2;
};
unsigned short raw;
};
With an enum I'd agree with GCC, it may have to truncate but that's because enums are not really separate from integers and pre-processor definitions. However an enum class is much stronger. If it is not strong enough to assume ALL Piece values taken as integers will be between 0 and 2 inclusive then the warning makes sense. Otherwise GCC is being needlessly picky and it might be worth mailing the list to say "look, this is a silly warning"
Incase anyone cannot see the point
You can store 4 distinct values in 2 bits of data, I only need 3 distinct values, so any enum of length 4 or less should fit nicely in the 2 bits given (and my enum does "derive" (better term?) from an unsigned type). If I had 5 or more THEN I'd expect a warning.

The warning issued by gcc is accurate, there's no need to compose a mail to the mailing list asking them to make the warning less likely to appear.
The standard says that an enumeration with the underlying type of unsigned char cannot be represented by a bitfield of length 2; even if there are no enumerations that holds such value.
THE STANDARD
The underlying value of an enumeration is valid even if there are no enum-keys corresponding to this value, the standard only says that a legal value to be stored inside an enumeration must fit inside the underlying type; it doesn't state that such value must be present among the enum-keys.
7.2 Enumeration declarations [dcl.enum]
7 ... It is possible to define an enumeration that has values not defined by any of its enumerators. ...
Note: the quoted section is present in both C++11, and the draft of C++14.
Note: wording stating the same thing, but using different terminology, can be found in C++03 under [dcl.enum]p6
Note: the entire [decl.enum]p7 hasn't been included to preserve space in this post.
DETAILS
enum class E : unsigned char { A, B, C };
E x = static_cast<E> (10);
Above we initialize x to store the value 10, even if there's no enumeration-key present in the enum-declaration of enum class E this is still a valid construct.
With the above in mind we easily deduce that 10 cannot be stored in a bit-field of length 2, so the warning by gcc is nothing but accurate.. we are potentially trying to store values in our bit-field that it cannot represent.
EXAMPLE
enum class E : unsigned char { A, B, C };
struct A {
E value : 2;
};
A val;
val.value = static_cast<E> (10); // OMG, OPS!?

According to the C++ Standard
8 For an enumeration whose underlying type is fixed, the values of the
enumeration are the values of the underlying type.
So the values of your enumeration are in the range
std::numeric_limits<unsigned char>::min() - std::numeric_limits<unsigned char>::max()
Bit field a defined as
Piece a:2;
can not hold all values of the enumeration.
If you would define an unscoped enumeration without a fixed underlying type then the range of its values would be
0 - 2

Yes, this warning is pointless because GCC already warns about assigning a value to a bitfield field (of enum type) that is truncated like this:
warning: conversion from 'Some_Enum' to 'unsigned char:2'
changes value from '(Some_Enum)9' to '1' [-Woverflow]
At the location of the warning it's only relevant that all declared enumerators can be held inside the bitfield field.
The statement that other values that are in the range of the underlying integer type (but don't correspond to a declared enumerator, which btw is well-defined, in general) can't be represented by the field, if ever assigned, is technically true, but has zero entropy, as a warning.
Thus, this warning was fixed in GCC 9.3.
IOW, GCC 9.3 and later don't warn about such code, anymore.

Related

Enumeration conversion and undefined behavior

From cpprefernce/static_cast/8:
A value of integer or enumeration type can be converted to any
complete enumeration type.
If the underlying type is not fixed, the
behavior is undefined if the value of expression is out of range (the
range is all values possible for the smallest bit field large enough
to hold all enumerators of the target enumeration).
I have two questions here:
How the underlying type of an enum cannot be fixed. Consider this example:
enum A : int { i = -1, b = 'c' };
Does the enum A's underlying type is fixed, regardless of the type of the enumerator values? Does the fixation of the underlying type is determined by either specifying the type or not, regardless of the type of the enumerator values? For example does this enum is fixed enum B { b, c }?
How I can determine the range of an enumeration. Consider this example:
enum N { c = 'A', hex = 0x64 };
Does the range of enum N is from 65 to 100? Hence the behavior is undefined in the following casts:
static_cast<N>(64) // UB?
static_cast<N>(101) // UB?
I'm going to write this with a focus on how to read cppreference.com. (This might make it seem more like I am answering one question instead of going over the limit.)
How the underlying type of an enum cannot be fixed.
This is a question about the enumeration type. So if the answer is not on the current page, the next place to look would be the page for
enumeration type.
Conveniently, on the page you linked to, the phrase "enumeration type" is linked, so you you don't have to search; just click the link.
Once you get to the enumeration type page, you are interested in "fixed". So do a find-in-page (ctrl-f) for "fixed".
The first occurrence of "fixed" is highly suggestive of what it means, while the second and third define the term in this context. The definition of an unscoped enumeration type whose underlying type is not fixed looks like form (1) in that section
enum A { i = -1, b = 'c' };
while the definition of an unscoped enumeration type whose underlying type is fixed looks like form (2) in that section
enum A : int { i = -1, b = 'c' };
If you specify the underlying type, then the underlying type is what you specify; it is fixed. If you do not specify the underlying type, then the underlying type is whatever the compiler decides to use; it is not determined in advance (i.e. not fixed).
How I can determine the range of an enumeration
This is given in your quote:
the range is all values possible for the smallest bit field large enough to hold all enumerators of the target enumeration
That's a lot of words, but just take it one piece at a time. Let's use your example:
enum N { c = 'A', hex = 0x64 };
The range is all values possible for the smallest bit field large enough to hold all enumerators of the target enumeration.
The range is all values possible for the smallest bit field large enough to hold all enumerators of N.
The range is all values possible for the smallest bit field large enough to hold 'A' and 0x64.
The range is all values possible for the smallest bit field large enough to hold 65 and 100.
The range is all values possible for the smallest bit field large enough to hold values representable with 7 bits (unsigned).
The range is all values possible for a bit field of length 7.
The range is 0 to 127.
While the compiler has some leeway in choosing the underlying type, the underlying type must be able to represent all enumerators. No matter what underlying type is chosen, it will be comprised of bits and be at least as large as the "smallest bit field" from the definition. The range of an enumeration consists of the values that will be representable no matter what underlying type is chosen. Values outside this range might fit in the underlying type, but they might not. Hence, whether or not they can be converted is undefined.

Which enum values are undefined behavior in C++14, and why?

A footnote in the standard implies that any enum expression value is defined behavior; why does Clang's undefined behavior sanitizer flag out-of-range values?
Consider the following program:
enum A {B = 3, C = 7};
int main() {
A d = static_cast<A>(8);
return d + B;
}
The output under the undefined behavior sanitizer is:
$ clang++-5.0 -fsanitize=undefined -ggdb3 enum.cc && ./a.out
enum.cc:5:10: runtime error: load of value 8, which is not a valid value for type 'A'
Note that the error is not on the static_cast, but on the addition. This is also true when an A is created (but not initialized) and then an int with value 8 is memcpyied into the A - the ubsan error is on the addition, not the initial load.
IIUC, ubsan in newer clangs does flag an error on the static_cast in C++17 mode. I don't know if that mode also finds an error in the memcpy. In any case, this question is focused on C++14.
The reported error comports with the following parts of the standard:
dcl.enum:
For an enumeration whose underlying type is fixed, the values of the enumeration are the values of the underlying type. Otherwise, the values of the enumeration are the values representable by a hypothetical integer types with minimal range exponent M such that all enumerators can be represented. The width of the smallest bit-field large enough to hold all the values of the enumeration type is M. It is possible to define an enumeration that has values not defined by any of its enumerators. If the enumerator-list is empty, the values of the enumeration are as if the enumeration had a single enumerator with value 0.100
So the values of the enumeration A are 0 through 7, inclusive, and the "range exponent" M is 3. Evaluating an expression of type A with value 8 is undefined behavior according to expr.pre:
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.
But there is one hiccup: that footnote from dcl.enum reads:
This set of values is used to define promotion and conversion semantics for the enumeration type. It does not preclude an expression of enumeration type from having a value that falls outside this range. [emphasis mine]
Question: Why is an expression with value 8 and type A undefined behavior if "[dcl.enum] does not preclude an expression of enumeration type from having a value that falls outside this range"?
Clang flags the use of static_cast on a value that is out of range. The behavior is undefined, if the integral value is not within the range of the enumeration.
C++ standard 5.2.9 Static cast [expr.static.cast] paragraph 7
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 enumeration value is unspecified / undefined (since C++17).
Note the phrasing of footnote 100: "[This set of values] does not preclude [stuff]." This is not an endorsement of the "stuff" as valid; it merely emphasizes that this section does not declare the stuff invalid. It is in fact a neutral statement that should bring to mind the fallacy of the excluded middle. As far as this section goes, values outside the values of the enumeration are neither approved nor disapproved. This section defines which values are outside the values of the enumeration, but it is left to other sections (like expr.pre) to decide the validity of using such values.
You can think of this footnote as a warning to those writing compilers: do not assume! An expression of enumeration type need not have a value within the enumeration's set of values. Such a case must compile correctly unless another section classifies that case as undefined behavior.
For a better understanding of what exactly clang is complaining about, try the following code:
enum A {B = 3, C = 7};
int main() {
// Set a variable of type A to a value outside A's set of values.
A d = static_cast<A>(8);
// Try to evaluate an expression of type A with this too-big value.
if ( !static_cast<bool>(static_cast<A>(8)) )
return 2;
// Try again, but this time load the value from d.
if ( !static_cast<bool>(d) ) // Sanitizer flags only this
return 1;
return 0;
}
The sanitizer does not complain about forcing a value of 8 into a variable of type A. It does not complain about evaluating an expression of type A that happens to have the value 8 (the first if). It does, though, complain when the value of 8 comes from (is loaded from) a variable of type A.
I'm not real familiar with Clang's compiler since I am accustomed to Visual Studio. I am currently using Visual Studio 2017. I was able to compile and run your code with language flag set to both c++14 and c++17 in both x86 and x64 debug builds. Instead of returning the addition in your example:
return d + B;
I had decided to output them to the console:
std::cout << (d + B);
and in all 4 cases my compiler printed out a value of 11.
I'm not sure about GCC either as I haven't tried it with your example, but this is leading me to believe that this is a compiler dependent issue.
I had followed your link and read section 8 in which you had referred to, but what caught my attention from that draft are the details coming from other sections namely 7 and 10.
Section 7 states:
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. If the enumerator-list is empty, the underlying type is as if the enumeration had a single enumerator with value 0.
But it is this sentence or clause that caught my attention:
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.
Section 10 states:
The value of an enumerator or an object of an unscoped enumeration type is converted to an integer by integral promotion. [ Example:
enum color { red, yellow, green=20, blue };
color col = red;
color* cp = &col;
if (*cp == blue) // ...
makes color a type describing various colors, and then declares col as an object of that type, and cp as a pointer to an object of that type. The possible values of an object of type color are red, yellow, green, blue; these values can be converted to the integral values 0, 1, 20, and 21. Since enumerations are distinct types, objects of type color can be assigned only values of type color.
color c = 1; // error: type mismatch, no conversion from int to color
int i = yellow; // OK: yellow converted to integral value 1, integral promotion
Note that this implicit enum to int conversion is not provided for a scoped enumeration:
enum class Col { red, yellow, green };
int x = Col::red; // error: no Col to int conversion
Col y = Col::red;
if (y) { } // error: no Col to bool conversion
— end example ]
It is these two lines that caught my attention:
color c = 1; // error: type mismatch, no conversion from int to color
int i = yellow; // OK: yellow converted to integral value 1, integral promotion
So let's look back at your example:
enum A {B = 3, C = 7};
int main() {
A d = static_cast<A>(8);
return d + B;
}
Here A is a complete type, B & C are the enumerators which are evaluated to a constant expression of integral type by promotion and are set to the values of 3 and 7 accordingly. This covers the declaration of enum A{...};
Inside of main() you now declare an instance or an object of A called d since A is a complete type. Then you assign d a value of 8 which is a constant expression or constant literal through the mechanism of static_cast. I'm not 100% sure if every compiler performs static_cast in the same exact manner or not; I'm not sure if this is compiler dependent.
So d is an object of type A, but since the value 8 is not in the list of enumerations, I believe this falls under the clause of implementation defined. This should then promote d to an integral type.
Then on your final statement where you return d+B.
Let's say that d was promoted to integral type with a value of 8, then you are adding the enumerated value of B which is 3 to 8, and therefore you should get an output of 11 in which I have in all 4 of my test cases on visual studio.
Now as for your compiler with Clang I can not say, but as far as I can tell this doesn't seem to produce any errors or undefined behavior at least according to Visual Studio. Then again because this code appears to be implementation defined, I think this relies heavily on your particular compiler and its version as well as the language version you are compiling it under.
I can not say that this will completely answer your question, but maybe it will shed some insight into the underlining workings of the compilers according to the documentations of the drafts & standards.
-Edit-
I decided to run this through my debugger and I put a break point on this line:
A d = static_cast<A>(8);
Then I stepped through to execute this line of code and looked at the value in the debugger. Here in Visual Studio, d does have a value of 8. However under its type it is listed as A and not int. So I do not know if this is a promoting it to int or not, or if it might happen to be a compiler optimization, something under the hood such as asm that is treating d as an int or unsigned int, etc.; but Visual Studio is allowing me to assign an integral value through a static_cast to an enumerated type. However, if I remove the static_cast it does fail to compile stating you can not assign a type int to a type A.
This leads me to believe that my original statement above is actually incorrect or only partially correct. The compiler is not fully "promoting" it to an integer type on assignment as d still remains an instance of A unless if it is doing so under the hood that I am unaware of.
I have not yet checked to see the asm of this code to see what assembly instructions are being generated by Visual Studio... therefore I am not currently able to make a full assessment at this point. Now, later on if I have more available time; I may look into it to see what asm lines are being generated by my compiler to see the underlying actions the compiler is taking.

how sizeof empty enum is 4 in C++?

Consider following program (See live demo here)
#include <iostream>
int main()
{
enum days{}d;
std::cout<<sizeof(d);
}
It prints 4 as an output on my local machine when compiling using g++ 4.8.1. How it occupies 4 bytes here? On gcc 6.0 in the given link I used `-pedantic-option also but still it compiles fine.
Then why it isn't allowed in C? I tried following program in gcc 4.8.1. (See live demo here )
#include <stdio.h>
int main(void)
{
enum days{}d;
printf("sizeof enum is %u",sizeof(d));
}
Compiler gives following errors:
4 12 [Error] expected identifier before '}' token
5 36 [Error] 'd' undeclared (first use in this function)
5 36 [Note] each undeclared identifier is reported only once for each function it appears in
Is it allowed to have empty enum in C++ but not in C?
C++ is not C. For C++, from [dcl.enum]:
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. If the enumerator-list is empty, the underlying type is as if the enumeration had a single enumerator with value 0.
So the underlying type of the enumerator (which determines its size) is as if it had a single 0 in it, though the actual type is implementation-defined. It could be 1 (int8_t certainly can hold 0), but definitely isn't larger than 4. In this case, you get 4, which is perfectly reasonable.
For C, the grammar simply requires having an enumerator.
As opposed to C, C++ does allow empty enumerations. [dcl.enum]/7:
If the enumerator-list is empty, the underlying type is as if the
enumeration had a single enumerator with value 0.
The underlying type (whose size is commonly also the enumerations size) is actually implementation-defined in your case, although most compilers will presumably choose int (and aren't allowed to chose anything larger here):
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.
C has the same requirements for the "underlying type" (although that exact notion doesn't exist in C), but its grammar does not allow for empty enumerations in the first place - §6.7.2.2/1:
enumerator-list: enumerator enumerator-list , enumerator
You are right. You cannot have an empty enumerator list in C. But you can have it in C++. See http://en.cppreference.com/w/c/language/enum and http://en.cppreference.com/w/cpp/language/enum
The C11 standard requires at least one enumerator in an enum declaration (section 6.7.2.2), the salient parts copied below:
enum-specifier:
enum identifieropt { enumerator-list }
enum identifieropt { enumerator-list }
enum identifier
enumerator-list:
enumerator
enumerator-list, enumerator
Sorry for the somewhat wonky formatting, I tried to recreate the passage from the (proposed) standard as close as I could.
In C++ the size is 4 bytes because your compiler chose int as the underlying integer type for the enum. Apparently sizeof(int) is 4 on your platform. It is very popular in the compiler world to default to int for enum representation (unless a larger type is required).
As for why it isn't allowed in C... Well, it isn't allowed in C because it isn't allowed in C. C is a completely different language with its own syntactic rules.

Constant enum size no matter the number of enumerated values

Why is the size of an enum always 2 or 4 bytes (on a 16- or 32-bit architecture respectively), regardless of the number of enumerators in the type?
Does the compiler treat an enum like it does a union?
In both C and C++, the size of an enum type is implementation-defined, and is the same as the size of some integer type.
A common approach is to make all enum types the same size as int, simply because that's typically the type that makes for the most efficient access. Making it a single byte, for example, would save a very minor amount of space, but could require bigger and slower code to access it, depending on the CPU architecture.
In C, enumeration constants are by definition of type int. So given:
enum foo { zero, one, two };
enum foo obj;
the expression zero is of type int, but obj is of type enum foo, which may or may not have the same size as int. Given that the constants are of type int, it tends to be easier to make the enumerated type the same size.
In C++, the rules are different; the constants are of the enumerated type. But again, it often makes the most sense for each enum type to be one "word", which is typically the size of int, for efficiency reasons.
And the 2011 ISO C++ standard added the ability to specify the underlying integer type for an enum type. For example, you can now write:
enum foo: unsigned char { zero, one, two };
which guarantees that both the type foo and the constants zero, one, and two have a size of 1 byte. C does not have this feature, and it's not supported by older pre-2011 C++ compilers (unless they provide it as a language extension).
(Digression follows.)
So what if you have an enumeration constant too big to fit in an int? You don't need 231, or even 215, distinct constants to do this:
#include <limits.h>
enum huge { big = INT_MAX, bigger };
The value of big is INT_MAX, which is typically 231-1, but can be as small as 215-1 (32767). The value of bigger is implicitly big + 1.
In C++, this is ok; the compiler will simply choose an underlying type for huge that's big enough to hold the value INT_MAX + 1. (Assuming there is such a type; if int is 64 bits and there's no integer type bigger than that, that won't be possible.)
In C, since enumeration constants are of type int, the above is invalid. It violates the constraint stated in N1570 6.7.2.2p2:
The expression that defines the value of an enumeration constant shall
be an integer constant expression that has a value representable as an
int.
and so a compiler must reject it, or at least warn about it. gcc, for example, says:
error: overflow in enumeration values
An enum is not a structure, it's just a way of giving names to a set of integers. The size of a variable with this type is just the size of the underlying integer type. This will be a type needed to hold the largest value in the enum. So as long as all the types fit in the same integer type, the size won't change.
The size of an enum is implementation-defined -- the compiler is allowed to choose whatever size it wants, as long as it's large enough to fit all of the values. Some compilers choose to use 4-byte enums for all enum types, while some compilers will choose the smallest type (e.g. 1, 2, or 4 bytes) which can fit the enum values. The C and C++ language standards allow both of these behaviors.
From C99 §6.7.2.2/4:
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.
From C++03 §7.2/5:
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. If the enumerator-list is empty, the underlying type is
as if the enumeration had a single enumerator with value 0. The value of sizeof() applied to an enumeration
type, an object of enumeration type, or an enumerator, is the value of sizeof() applied to the
underlying type.
It seems to me that the OP has assumed that an enum is some kind of collection which stores the values declared in it. This is incorrect.
An enumeration in C/C++ is simply a numeric variable with strictly defined value range. The names of the enum are kind of aliases for numbers.
The storage size is not influenced by the amount of the values in enumeration. The storage size is implementation defined, but mostly it is the sizeof(int).
The size of an enum is "an integral type at least large enough to contain any of the values specified in the declaration". Many compilers will just use an int (possibly unsigned), but some will use a char or short, depending on optimization or other factors. An enum with less than 128 possible values would fit in a char (256 for unsigned char), and you would have to have 32768 (or 65536) values to overflow a short, and either 2 or 4 billion values to outgrow an int on most modern systems.
An enum is essentially just a better way of defining a bunch of different constants. Instead of this:
#define FIRST 0
#define SECOND 1
...
you just:
enum myenum
{ FIRST,
SECOND,
...
};
It helps avoid assigning duplicate values by mistake, and removes your need to even care what the particular values are (unless you really need to).
The big problem with making an enum type smaller than int when a smaller type could fit all the values is that it would make the ABI for a translation unit dependent on the number of enumeration constants. For instance, suppose you have a library that uses an enum type with 256 constants as part of its public interface, and the compiler chooses to represent the type as a single byte. Now suppose you add a new feature to the library and now need 257 constants. The compiler would have to switch to a new size/representation, and now all object files compiled for the old interface would be incompatible with your updated library; you would have to recompile everything to make it work again.
Thus, any sane implementation always uses int for enum types.

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.