1

How can I ensure COLOR_NAMES is filled to the correct size at compile-time? If a new color is added, say COLOR_4 (and hence N_COLORS is auto incremented), the compiler will then tell me COLOR_NAMES is not filled to size.

Most answers that I found online is for runtime, not compile time.

This is for C-style notation (no STL and other libraries usage).

enum Colors
{
   COLOR_1,
   COLOR_2,
   COLOR_3,
   N_COLORS;
};

const char* COLOR_NAMES[N_COLORS] =
{
   /* COLOR_1 */ "Color1",
   /* COLOR_2 */ "Color2",
   /* COLOR_3 */ "Color3"
};

const char* Blah()
{
   Colors color;
   ...
   printf("%s blah blah\n", COLOR_NAMES(color));
}
Ryuu
  • 779
  • 11
  • 22

3 Answers3

2

It is standard practice for such cases of arrays and corresponding enums to compare the "enum size member", N_COLORS in your case, against the number of items in the array.

To get the number of items in an array, simply divide the array size with the size of one array member.

Thus:

_Static_assert(sizeof(COLOR_NAMES)/sizeof(*COLOR_NAMES) == N_COLORS, 
               "Array item missing!");

Edit:

Oh btw for this to be meaningful the array declaration must be const char* COLOR_NAMES[] = otherwise you wouldn't be able to tell if there are missing initializers in the array initialization list.

Lundin
  • 195,001
  • 40
  • 254
  • 396
1

The ideal would to be able to use sizeof from preprocessor. But we can't because sizeof is evaluated by the compiler.

There are many ways to bypass this but here is quite simple and portable one:

const char* COLOR_NAMES[] = {
   /* COLOR_1 */ "Color1",
   /* COLOR_2 */ "Color2",
   /* COLOR_3 */ "Color3"
};

typedef char CHECK_COLOR_NAMES[sizeof(COLOR_NAMES) / sizeof(COLOR_NAMES[0]) == N_COLORS ? 1 : -1];

If the test fails, you will attempt to define an array with size -1, which will result into a compilation error.

EDIT: Then we use a typedef to avoid to actually create a variable that we'll not use (Lundin's remark)

mikedu95
  • 1,725
  • 2
  • 12
  • 24
  • 1
    When creating a "poor man's static assertion", it is better to use a `typedef`. That way you don't actually allocate any memory for your nonsense array. Better yet: get a C compiler which is not older than 5 years and you'll have language support for static assert. – Lundin Jan 08 '16 at 07:21
  • And what's the portable way to do static assertion with your one second old compilers ? Thanks. – mikedu95 Jan 08 '16 at 09:35
  • 1
    I think the presence of static assert in C11 alone is a reason to upgrade to that standard. The old "cannot declare an array of size -1 at line 666" compile-time assert errors are not very descriptive :) – Lundin Jan 08 '16 at 09:40
  • Ah okay, thanks ! I've been always reticent about standard static assertions because VS (including VS2015) just doesn't know _Static_assert, and in general simply ignores C11. But I've just learnt that C11 also defines static_assert in assert.h. In VS, this is a keyword, not a macro in assert.h :P. But that matters very little. So I think the best way to combine theory and practice nowadays is to include and use static_assert, for maximum portability. Thank you again Lundin :). – mikedu95 Jan 08 '16 at 10:09
  • It is because VS is mainly a C++ compiler and `static_assert` is a keyword in C++. In addition to the `_Static assert` keyword in C11, it also included `static_assert` as a macro, for C++ compatibility. Reason why VS2015 in C mode doesn't understand `_Static_assert`, is because despite the 2015 in the name, it's still an old crap compiler from the mid 90s. I would avoid that compiler completely for pure C programming. – Lundin Jan 08 '16 at 10:14
  • I did approximation when I said VS but I did mean Microsoft C (I say C, not C++). You cannot avoid a compiler just for ideological reasons. You have better to choose Microsoft C to program native Windows apps in C, and you definitively should use it if you want to write kernel-mode drivres for windows, and that are all just examples. – mikedu95 Jan 08 '16 at 10:30
  • Also, many other compilers just don't care about anything else than ANSI C (now I really mean ANSI :D, not an approximation to mean ISO C90 or 95 ...). And even ANSI C may be not fully supported (void main instead of int main for example). These are typically compiler for embedded systems. You cannot avoid them just because they don't implement C11. – mikedu95 Jan 08 '16 at 10:33
  • Sure you can avoid them if they refuse to adopt current standards. To drop a real life anecdote: At one point I was about to select a compiler for a project & therefore chatting with a sales person from one of them embedded systems compilers. To the question "do you support C11" they replied "what is that?". So I went with gcc instead and the business opportunity was lost to that company. If you have the chance, just refuse to work with old or non-standard compilers. – Lundin Jan 08 '16 at 10:59
0

with constepxr, you can use :

constexpr static const char* COLOR_NAMES[N_COLORS] =
{
   /* COLOR_1 */ "Color1",
   /* COLOR_2 */ "Color2",
   /* COLOR_3 */ "Color3"
};

static_assert(COLOR_NAMES[N_COLORS-1] !=0);

if you want to use local variable, you can use variant

constexpr variant<const char*> COLOR_NAMES[N_COLORS] =
{
   /* COLOR_1 */ "Color1",
   /* COLOR_2 */ "Color2",
   /* COLOR_3 */ "Color3"
};

static_assert(get<0>(COLOR_NAMES[N_COLORS-1]) !=nullptr);
camino
  • 10,085
  • 20
  • 64
  • 115
  • What if the memory space just outside of COLOR_NAMES is not zero in the first place? – Ryuu Jun 07 '18 at 02:37
  • 1
    @Ryuu according to this link: https://stackoverflow.com/a/7760316/440403, if T is not a class(T array[]), then its element will not be initialized. but per https://stackoverflow.com/a/20916572/440403, static variable will be initialized with default value – camino Jun 07 '18 at 14:29
  • 1
    @Ryuu for local variable, you can use constexpr together with variant, since variant guarantee the first element will be initialized with default value. I got the idea from here: https://www.bfilipek.com/2018/06/variant.html – camino Jun 07 '18 at 19:34
  • Cool article. Thanks! – Ryuu Jun 08 '18 at 04:56