If I have more than one enum
, eg:
enum Greetings{ hello, bye, how };
enum Testing { one, two, three };
How can I enforce th
This is the answer you don't want to hear. In C, you can't really. Now if your C code were in the "Clean C" subset of C++, you could compile with the C++ compiler to get all the errors of using the wrong enum/int values, etc.
Clang produces the following warning, which is the best you can do (Although the user could upgrade the warning to an error).
enum Greetings { hello, bye, how };
enum Count { one, two, three };
void takes_greeting(enum Greetings x) {}
void takes_count(enum Count x) {}
int main() {
takes_greeting(one);
takes_count(hello);
}
Compiler output:
cc foo.c -o foo
foo.c:8:17: warning: implicit conversion from enumeration type 'enum Count' to different enumeration type 'enum Greetings' [-Wenum-conversion]
takes_greeting(one);
~~~~~~~~~~~~~~ ^~~
foo.c:9:14: warning: implicit conversion from enumeration type 'enum Greetings' to different enumeration type 'enum Count' [-Wenum-conversion]
takes_count(hello);
~~~~~~~~~~~ ^~~~~
If users are going to ignore errors and warnings from the compiler, then there's not much you can do to help them.
If you also want to ensure the valid range, there's a technique that comes with the little overhead of a pointer dereference for getting the integer value -- and a lot of boilerplate typing. It might still be useful because it leverages you from writing range-checking code where it would be otherwise necessary.
greetings.h:
#ifndef GREETINGS_H
#define GREETINGS_H
struct greetings;
typedef struct greetings Greetings;
extern const Greetings * const Greetings_hello;
extern const Greetings * const Greetings_bye;
extern const Greetings * const Greetings_how;
const char *Greetings_str(const Greetings *g);
int Greetings_int(const Greetings *g);
#endif
greetings.c:
#include "greetings.h"
struct greetings {
const int val;
};
static const Greetings hello = { 0 };
static const Greetings bye = { 1 };
static const Greetings how = { 2 };
const Greetings * const Greetings_hello = &hello;
const Greetings * const Greetings_bye = &bye;
const Greetings * const Greetings_how = &how;
static const char * const Greetings_names[] = {
"hello",
"bye",
"how"
};
const char *
Greetings_str(const Greetings *g)
{
return Greetings_names[g->val];
}
int
Greetings_int(const Greetings *g)
{
return g->val;
}
example main.c:
#include <stdio.h>
#include "greetings.h"
void
printTest(const Greetings *greeting)
{
if (greeting == Greetings_how) return;
puts(Greetings_str(greeting));
}
int
main()
{
const Greetings *g = Greetings_hello;
printTest(g);
}
Yes, a LOT to type, but you get full type and range safety. No other compilation unit can ever instantiate a struct greetings
, so you are completely safe.
Edit 2015-07-04: For protecting against NULL, there are two possibilities. Either use NULL for the default value (#define Greetings_hello 0
instead of the pointer used now). This is very convenient, but drops type safety for default enum values, NULL can be used for any enum. Or declare it invalid and either check for it in the accessor methods, returning an error, or use something like GCCs __attribute__((nonnull()))
to catch it at compile time, eg in greetings.h:
const char *Greetings_str(const Greetings *g)
__attribute__((nonnull(1)));
Unfortunately enum
are a weak point in the type system of C. Variables of an enum
type are of that enum
type, but the constants that you declare with the enum
are of type int
.
So in your example
enum Greetings{ hello, bye, how };
enum Testing { one, two, three };
enum Greetings const holla = hello;
enum Testing const eins = one;
hello
and one
are two names for the same value, namely 0
, and with the same type int
.
holla
and eins
again have value 0
, but have their respective types.
If you want to force some "official" type safety for "real" constants, that is entities that have the type and value that you want, you'd have to use some more involved constructs:
#define GREETING(VAL) ((enum Greetings){ 0 } = (VAL))
#define HELLO GREETING(hello)
The assignment in the GREETING
macro ensures that the result is an "rvalue" so it can't be modified and will be taken by the compiler just for its type and value.
In C, you can fake it with boilerplate code.
typedef enum { HELLO_E, GOODBYE_E } greetings_t;
struct greetings { greetings_t greetings; };
#define HELLO ((struct greetings){HELLO_E})
#define GOODBYE ((struct greetings){GOODBYE_E})
typedef enum { ONE_E, TWO_E } number_t;
struct number { number_t number; };
#define ONE ((struct number){ONE_E})
#define TWO ((struct number){TWO_E})
void takes_greeting(struct greetings g);
void takes_number(struct number n);
void test()
{
takes_greeting(HELLO);
takes_number(ONE);
takes_greeting(TWO);
takes_number(GOODBYE);
}
This should not incur any overhead, and produces errors instead of warnings:
$ gcc -c -std=c99 -Wall -Wextra test2.c test2.c: In function ‘test’: test2.c:19: error: incompatible type for argument 1 of ‘takes_greeting’ test2.c:20: error: incompatible type for argument 1 of ‘takes_number’
Notice that I'm not using GNU extensions, and no spurious warnings are generated. Only errors. Also note that I'm using a version of GCC that's as old as dirt,
$ gcc --version powerpc-apple-darwin9-gcc-4.0.1 (GCC) 4.0.1 (Apple Inc. build 5493) Copyright (C) 2005 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
This should work with any compiler with support for C99's compound literals.
You can typedef your enums and then declare variables and function arguments of those types.