0

I was browsing a C source file (4cpp_lexer_types.h) in https://4coder.handmade.network/static/media/file/4coder/fcpp-lexer-1.1.zip and found the following code and cannot understand the use/benefit of this.

#define ENUM(type,name) typedef type name; enum name##_
ENUM(uint32_t, Cpp_Token_Type){
    CPP_TOKEN_JUNK = 0,
    CPP_TOKEN_COMMENT = 1,
    .
    .
    .
};

From the #define I can deduce that that post preprocessing, code would look like this.

typedef uint32_t Cpp_Token_Type; enum Cpp_Token_Type_{
    CPP_TOKEN_JUNK = 0,
    CPP_TOKEN_COMMENT = 1,
    .
    .
    .
};

Why not simply typedef the enum like the following?

typedef enum {
    CPP_TOKEN_JUNK = 0,
    CPP_TOKEN_COMMENT = 1,
    .
    .
    .
}Cpp_Token_Type;

I both cases the usage will be the same:

Cpp_Token_Type t = CPP_TOKEN_JUNK;

So my question is why do this, is this some standard practice that aims for some specific result?

AndersK
  • 35,813
  • 6
  • 60
  • 86
Arjob Mukherjee
  • 387
  • 1
  • 10

2 Answers2

3

In the original code, Cpp_Token_Type is a uint32_t, whereas in your simplification Cpp_Token_Type is an enum type. If you were to create a variable of type Cpp_Token_Type it would behave quite differently in the two cases. E.g., in your example, t is signed with your definition, unsigned in the original.

Doug Currie
  • 40,708
  • 1
  • 95
  • 119
  • Hi Doug, considering this is a piece of C code, can you explain what could be the difference? I mean enums are int anyways, may not be uint32 but standard int. – Arjob Mukherjee Jan 10 '19 at 18:37
  • 1
    In your example, `CPP_TOKEN_JUNK ` is converted to unsigned before assignment to `t` so it's irrelevant that it is an `int`. There are lots of ways that signed numbers differ from unsigned. One example is that signed operations can overflow, and lead to dreaded undefined behavior. When `t` is unsigned, using it in expressions will cause the other operands to be promoted to unsigned ints. – Doug Currie Jan 10 '19 at 18:49
  • Yes I guess that's the only thing it is doing. It does an automatic cast. – Arjob Mukherjee Jan 10 '19 at 19:07
  • @Mike, enumeration constants are always `int`, but enumeration variables are of a type selected by the compiler; see https://stackoverflow.com/a/366033/33252 – Doug Currie Jan 10 '19 at 23:06
0

This is an emulation of typed enums that we have in C++, in a way that is consistent with another popular enum trick to implement flag types.

The idea is now you can do this:

struct My_Struct
{
    Cpp_Token_Type type;
};

regardless of the underlying integer type used, and the type will be exactly what you want it to be. Without this, the type of the enum will be whatever the compiler chooses for it (most often an int). If you decided later that you wanted to represent the enum as a uint16_t, you could change the definition to

ENUM(uint16_t, Cpp_Token_Type) {...};

In this case, it's emulating this code from C++:

enum Cpp_Token_Type : uint16_t {...};

from C++, albeit with less type safety and less casting.

This is consistent with implementing flag types with enums like so:

enum My_Flags_ {
    MY_FLAGS_ONE = 0x1,
    MY_FLAGS_TWO = 0x2,
};
typedef int My_Flags;

which would be equivalent to

ENUM(int, My_Flags) {
    MY_FLAGS_ONE = 0x1,
    MY_FLAGS_TWO = 0x2,
};

This is done in C++ code to avoid writing templatized structs with operator overloading just to get flag types. Otherwise, MY_FLAGS_ONE | MY_FLAGS_TWO would be an error (when compiled as C++).

For reference, Dear Imgui uses this all over the place, whereas the Vulkan C++ headers use templatized structs.

rationalcoder
  • 1,587
  • 1
  • 15
  • 29