Reputation: 6695
I'd like the compiler to give a warning like:
"Banana is not a Color."
I understand that in the context of a switch statement the labels are promoted to int an the compiler is happy with 0 and doesn't care if it is "Green" or "Banana".
I was hoping -Wconversion for GCC would do the trick.
enum Color
{
Green = 0
};
enum Fruit
{
Banana = 0
};
int main()
{
Color c = Green;
switch (c)
{
case Banana:
std::cerr << "Banana" << std::endl;
break;
}
return 0;
}
Upvotes: 10
Views: 354
Reputation: 88711
C++11 introduces strongly typed enum
s, using enum class
:
#include <iostream>
enum class Color
{
Green = 0
};
enum class Fruit
{
Banana = 0
};
int main() {
Color c = Color::Green;
switch (c)
{
case Fruit::Banana:
std::cerr << "Banana" << std::endl;
break;
}
return 0;
}
This code will fail exactly as you hoped:
test.cc:18:17: error: could not convert '(Fruit)0' from 'Fruit' to 'Color'
Note: enum class
doesn't cause Green
and Banana
to be in the enclosing namespace anymore, so you have to explicitly write Color::
and Fruit::
now, but you do also get the typesafety.
I don't think warning on this in C++03 would make much sense, it would basically just become noise.
People use enum
s as compile-time constants quite often, even for things like bit-fields. For the warning to be meaningful you'd have to catch things like enum { foo=0xf }; int c = foo;
and many codebases are scattered with int
/enum
conversions. (Allowing this would defeat the point of any stronger type checking).
Worse still though would be enum
s used in almost any kind of meta programming context, where anonymous enum
s are not only used freely interchangeably with int
types on a regular basis:
template <int I>
struct is_odd {
enum { value = !(I % 2) };
};
template <int I>
struct foo {
static void bar() { /* I is true */ }
};
template <>
struct foo<0> {
static void bar() { /* I is false */ }
};
int main() {
foo<is_odd<201>::value>::bar();
int i = is_odd<200>::value;
}
but they're also used recursively as local storage:
template <int N>
struct factorial {
enum {
// This enum is *not* compatible with the one in N-1
value = N * factorial<N - 1>::value
};
};
template <>
struct factorial<0> {
enum { value = 1 };
};
Which is a part of the reason why enum class
was required in order to introduce a non-breaking way of adding type-safety over the current state of enum
s in C++. There would be so many warnings from existing code that a warning would be next to useless because of things like this.
Even in the fairly simple switch statement example you showed, something like this is legal:
#include <iostream>
enum Color { Green = 0x1, Red = 0x2 };
enum Fruit { Banana = 0x3 };
int main() {
int v = Green|Red;
Color c = Color(v);
switch (c) {
case Banana:
std::cerr << "Banana" << std::endl;
break;
}
return 0;
}
Although this is legal here it's not hugely meaningful, but things like that are used fairly regularly and meaningfully in "bit-twiddling" C code still. The point of this example is that by allowing one int
<->enum
conversion anywhere it effectively means that being strict about the type of the enum
later on is rendered meaningless. In the general case you can't detect if this kind of conversion has happened (it might have been in a different translation unit).
enum class
is by far the nicest way of introducing such strictness cleanly without adverse effects on existing code.
Upvotes: 16
Reputation: 8147
In C++11 (the new C++ standard) you can use enum class
to create a strongly typed enum. See the Wikipedia article on C++11.
Example:
enum class Color { Green };
enum class Fruit { Banana };
// Put your main here - it would now fail
Upvotes: 6