2

I have learned from school-books that a typical definition of an enum if like this:

enum weather {
    sunny,
    windy,
    cloudy,
    rain,
} weather_outside;

and then declare a var like:

enum weather weather_outside = rain;

My question is, if it is possible to use enumerated constants just by saying e.g. rain which keeps the integer 3, what is exactly the use and the point of having a type-like more complicated deceleration as enum weather weather_outside = rain; to have weather_outside being equal to 3 (since enum values can only be compile-time constants)? Why not just use a const or a macro for it? I am a bit confused whether enums are really necessary at all?!

Student
  • 708
  • 4
  • 11
  • You don't have to define a named type if you don't want to. As of C11 anonymous enums are a thing. – mediocrevegetable1 Aug 21 '21 at 08:10
  • And for an example where the type may be useful, `enum text_color { ... }; void set_text_color(enum text_color col);` – mediocrevegetable1 Aug 21 '21 at 08:12
  • I can as well call `set_text_color(2)` and get no warning what so ever from my compiler! – Student Aug 21 '21 at 08:28
  • 2
    Unfortunately `enum`s lack much type safety in general so you can even use an `int` that's out of bounds of the actual `enum` list. Related to that issue: [Can enums be considered unsafe?](https://stackoverflow.com/q/65903557), [How to create type safe enums?](https://stackoverflow.com/q/43043246) – mediocrevegetable1 Aug 21 '21 at 08:34

6 Answers6

4

Enumerations in C are largely for convenience, as they are not strongly typed. They were created to express named options, but limitations of language development and the ways people adopted them for other uses led to the current situation where they are little more than named integer values.

Enumerations support situations where we have various distinct options, such as the weather conditions you show, and want to name them. Ideally, enumerations would be strongly typed, so that rain would not be easily convertible to 3 or vice-versa; writing either int x = rain; or enum weather x = 3; would yield a warning or error from the compiler.

However, there are problems doing this. Consider when we want to write code that processes all values in an enumeration, such as:

for (enum weather i = sunny; i <= rain; i = i+1)
    DoSomethingWithWeatherCondition(i);

Take a look at that update expression, i = i+1. It is perfectly natural to an experienced C programmer. (We could write i++, but that is the same thing, and it is spelled out here for illustration.) We know it updates i to the next value. But, when we think about it, what is i+1? i is an enumeration value, and 1 is an int. So we are adding two different things.

To make that work, C treated enumeration values as integers. This allows i+1 to be calculated in the ordinary way as the addition of two integers. Further, then the result is an int, and we have i = some int result, which means we have to allow assigning an int to an enum weather.

Maybe one solution to this would have been to define addition of enumeration values and integers, so that i+1 would not need to treat i as an integer; it would just be define to return the next value in the enumeration after i. But early C development did not do this. It would have been more work, and new features in programming languages were not developed all at once with foresight about what would be useful or what problems might arise. They were often developed bit-by-bit, trying out new things with a little prototype code.

So, enumeration values were integers. Once they were integers, people started using them for purposes beyond the simple original purpose. Enumerations were useful for defining constants that could be used where the compiler needed constant expressions, including array dimensions and initial values for static objects. const did not exist at the time, but it would not have served because, having defined const int x = 3; or even static const int x = 3;, we could not use that x in float array[x];. (Variable-length arrays did not exist at the time, and even now they are not available for static objects.) We also could not use x in int m = 2*x+3; when the definition of m is outside of a function (so it defines a static object). However, if x were defined as an enumeration value rather than an int, it could be used for these purposes.

This lead to enumerations being used in situations where things were not really being enumerated. For example, they are often used for bit-masks of various kinds:

enum
{
    DeviceIsReadable           = 1,
    DeviceIsWriteable          = 2,
    DeviceSupportsRandomAccess = 4,
    DeviceHasFeatureX          = 8,
    …
}

Once people started using enumerations this way, it was too late to make enumerations strongly typed and define arithmetic on them. These bit masks have to be usable with the bitwise operators |, &, and ^, not just +1. And people were using them for arbitrary constants and arithmetic on them. It would have been too difficult to redefine this part of the C language and change existing code.

So enumerations never developed as properly separate types in C.

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
1

This is not the correct syntax:

warning: unused variable 'weather_outside'

This example:

enum light {green, yellow, red};

enum weather { sunny, windy, cloudy, rain,};

enum weather wout;
wout = red;  // mismatch

gives a warning with -Wextra:

implicit conversion from 'enum light' to 'enum weather'

This can help prevent errors.


const was not there in the beginning and can be a good alternative; but with an enum you do not have to assign a number - but you can:

enum {sunny, windy, cloudy, 
      rain = 100, snow}

This must be the most compact way to get two separated regions (0,1,2,100,101).

0

Functionally the two methods are equivalent, but enums allow you to better express that something is one of several named choices. It is easier to understand "Rainy" than "3", or "South" rather than "2". It also puts a limit on which values the enumeration can take*.

Using a typedef can help in making the code less verbose:

typedef enum
{
    SUNNY,
    WINDY,
    CLOUDY,
    RAIN
} Weather;

Weather weather_outside = RAIN;

switch (weather_outside)
{
    case SUNNY:
        printf("It's sunny\n");
        break;

    case WINDY:
        printf("It's windy\n");
        break;

    // ...
}

An additional advantage here is that the compiler may emit a warning if not all enumerated values are handled in the switch, which is wouldn't have if weather_outside was an integer.

Taking a look at function declarations, this:

void takeStep(Direction d)

is more expressive than:

void takeStep(int d)

Of course, you could write int direction, but this is using a variable name to express a type.

[*] It is technically allowed to write Weather weather_outside = 12, as enum values are integer constants, but this should look like a code smell.

Yun
  • 3,056
  • 6
  • 9
  • 28
  • My question was exactly why should I use enums as a type alias? Is it necessary for RAIN to be of type Weather, since it is basically just 3! And to avoid using **magical numbers** I could also just use a macro as well `#define RAIN 3`. – Student Aug 21 '21 at 09:20
  • 2
    @Student You don't _have_ to use enums, but they can express things that macros cannot. E.g., `#define RAIN 3` doesn't express that RAIN is one of several modes of the type `Weather`. This makes the code easier to understand and enables compiler warnings such as the ones Jacon and I described. – Yun Aug 21 '21 at 09:28
0

Your code is invalid. When you write

enum weather {
    sunny,
    windy,
    cloudy,
    rain,
} weather_outside;

you already declared a new type called enum weather and a new variable named weather_outside. Doing enum weather weather_outside = rain; will create a new variable with the same name so all compilers I've tried emit errors on that

So the correct way is to remove the first variable definition

enum weather {
    // ...
};
enum weather weather_outside = rain;

or use typedef to avoid the use of enum everywhere

typedef enum {
    // ...
} weather;
weather weather_outside = rain;

The latter may not be good practice in C due to namespace pollution, and is prohibited in Linux kernel


Back to the main question.

what is exactly the use and the point of having a type-like more complicated deceleration as enum weather weather_outside = rain; to have weather_outside being equal to 3 (since enum values can only be compile-time constants)? Why not just use a const or a macro for it? I am a bit confused whether enums are really necessary at all?!

Semantically 3 doesn't mean anything, and nothing prevents rain from changing value when a new enum member is inserted before it. A named value is always better than a magic number. Besides that way it limits the range of values that weather_outside can accept. If you see or have to do weather_outside = 123 then you know there's something wrong

And to avoid using magical numbers I could also just use a macro as well #define RAIN 3

ALL CAPS ARE HARDER TO READ, and macros are generally discouraged over inline (for functions) or const (for values). But most importantly:

  • enum allows the debugger to show the current value as name, which is super helpful when debugging. No one knows what 123 means but they surely understand what windy represents

    It may be not as useful in this example, but suppose you have a huge enum of 200 different values, how do you know what the 155th item is without counting? The middle items may also be renumbered so their values doesn't correspond to their positions anymore

    I'm sure you won't be able to remember all those 200 values when you have 200 const or #define lines. Keep looking at the header file for the value is tedious. And how would you get values of const int my_weather = sunny | windy or #define RAIN (cloudy + floody)? No need to keep track of those with enum. It just works

    enum {
      sunny = X,
      windy = Y,
      my_weather = sunny | windy,
      cloudy,
      floody,
      rain = cloudy + rain
    }
    
  • enum allows you to use the constant in an array declaration

    enum array_items {
        SPRING,
        SUMMER,
        FALL,
        WINTER,
        NUMBER_OF_SEASONS
    };
    
    int a[NUMBER_OF_SEASONS] = { 1, 2, 3, 4};
    // const int MAX_LENGTH = 4;
    // int b[MAX_LENGTH]; /* doesn't work without VLA */
    return a[0];
    

And for an example where the type may be useful, enum text_color { ... }; void set_text_color(enum text_color col); – mediocrevegetable1

I can as well call set_text_color(2) and get no warning what so ever from my compiler!

It's a limitation of C and gcc, because enum is just an integer type in C instead of a real type like in C++, so probably gcc can't do a lot of checks for it. But ICC can warn you about that. Clang also has better warnings than gcc. See:

Sure enum in C doesn't prevent you from shooting yourself like enum class in C++ but it's much better than macros or constants

phuclv
  • 37,963
  • 15
  • 156
  • 475
  • 1
    Re “Your code is invalid.”: Submitters commonly elide code, and one not uncommon pattern is that a declaration for that such as their `enum weather` is external (outside any function), and the `enum weather weather_outside = rain;` is inside `main` or another function. Then there will be no error. The fact that OP did not report such an error is a clue that this is the case. – Eric Postpischil Aug 21 '21 at 11:29
0

Yes, at one level, using an integer variable and a set of preprocessor #defines is just about completely equivalent to using an enum. You achieve the same things: A small number of distinct values, with no necessary numeric interpretation, encoded compactly as (generally) small integers, but represented in source code by more meaningful symbolic names.

But the preprocessor is, to some extent, a kludge, and modern practice recommends avoiding its use when possible. And enums, since they are known to the compiler proper, have a number of additional advantages:

  • type safety — the compiler can warn you if you use values that don't belong (e.g. weather = green)
  • debugging — a debugger can show you the value of an enumeration as its symbolic name, not as an inscrutable number
  • additional warnings — the compiler can warn you if you do a switch on an enumeration but forget one of the cases
Steve Summit
  • 45,437
  • 7
  • 70
  • 103
  • enum values are different from `#define` identifiers: among other advantages, you can reuse enum value names for local variables and struct member, whereas preprocessor defines may prevent compilation or cause weird behavior. – chqrlie Aug 21 '21 at 12:04
-1

Enums are integers and can be used as constant expressions.

enum weather {
    sunny,
    windy,
    cloudy,
    rain,
} weather_outside;

int main(void)
{
    int weather = cloudy;
    printf("%d\n", rain);
    printf("`weather`==%d\n", weather);
}

https://godbolt.org/z/939KvreEY

0___________
  • 60,014
  • 4
  • 34
  • 74