2
\$\begingroup\$

I am building an application with a lot of integer values which need to be mapped to strings for user CLI and logging.

Using array LUT does not provide boundary checks so I want to generate conversion functions to make sure invalid values will be handled properly.

The idea is to use a function like this:

void *enumStr(void *e, bool isToStr) 
{
 if (isToStr && e == NUM_ZERO) return "zero"; if (!isToStr && strcmp(e, "zero") == 0) return 0;
 if (isToStr && e == NUM_ONE) return "one"; if (!isToStr && strcmp(e, "one") == 0) return 1;
 if (isToStr && e == NUM_TWO) return "two"; if (!isToStr && strcmp(e, "two") == 0) return 2;
 if (isToStr) return "ENUM_N/A"; else return UINT_MAX; 
}

which converts between a number and a string and to add two helper functions which hide the boolean argument.

#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <limits.h>
#define ENUMSTR_START(enumName) \
 void * _ ## enumName ## _(void *e, bool isToStr) {
 
#define ENUMSTR_ITEM(enumItem, enumStr) \
 if (isToStr && e == enumItem) return enumStr; if (!isToStr && strcmp(e, enumStr) == 0) return enumItem
#define ENUMSTR_END(enumName) \
 if (isToStr) return "STR_N/A"; else return UINT_MAX; } \
 const char * enumName ## _toStr(uint32_t enumItem) { return (const char * ) _ ## enumName ## _ (enumItem, true); } \
 uint32_t enumName ## _fromStr(const char *enumStr) { return _ ## enumName ## _ (enumStr, false); }
typedef enum
{
 NUM_ZERO,
 NUM_ONE,
 NUM_TWO
} NUM;
ENUMSTR_START(NUM)
 ENUMSTR_ITEM(NUM_ZERO, "zero");
 ENUMSTR_ITEM(NUM_ONE, "one");
 ENUMSTR_ITEM(NUM_TWO, "two");
ENUMSTR_END(NUM)
int main(void) {
 printf("test 0 %s\n", NUM_toStr(NUM_ZERO));
 printf("test 1 %s\n", NUM_toStr(NUM_ONE));
 printf("test 2 %s\n", NUM_toStr(NUM_TWO));
 printf("test 32 %s\n", NUM_toStr(32));
 printf("test zero %u\n", NUM_fromStr("zero"));
 printf("test one %u\n", NUM_fromStr("one"));
 printf("test two %u\n", NUM_fromStr("two"));
 printf("test three %u\n", NUM_fromStr("three"));
 return 0;
}

Output:

test 0 zero
test 1 one
test 2 two
test 32 STR_N/A
test zero 0
test one 1
test two 2
test three 4294967295
asked Aug 16, 2024 at 18:04
\$\endgroup\$
6
  • \$\begingroup\$ Returning 1 from the function void *enumStr(void *e, bool isToStr) is not valid C as arbitrary integer values cannot certainly convert to a void *. You need a new approach. Please explain more about your needs and usage limitations. \$\endgroup\$ Commented Aug 17, 2024 at 6:02
  • \$\begingroup\$ "mapped to strings" --> what will be the longest string? Is it unbounded? \$\endgroup\$ Commented Aug 17, 2024 at 6:06
  • \$\begingroup\$ @chux-ReinstateMonica can you explain more about void*? Can't I assign any value I want to a pointer? \$\endgroup\$ Commented Aug 17, 2024 at 15:49
  • \$\begingroup\$ @chux-ReinstateMonica About the needs: I have a lot of enums which must be associated with strings so that I can convert strings from user input to enums and convert enums to strings when writing log messages. \$\endgroup\$ Commented Aug 17, 2024 at 15:56
  • \$\begingroup\$ "lot of enums" --> 10s 1000s 10000s of them? String lengths, what range? 0, 1, 10, 100 1000? \$\endgroup\$ Commented Aug 17, 2024 at 16:44

1 Answer 1

2
\$\begingroup\$

Macro names are all-caps - that's conventional and good.

Enum values are also all-caps - that makes them look like macros, which is not good.


#define ENUMSTR_START(enumName) \
 void * _ ## enumName ## _(void *e, bool isToStr) {

This appears to create a function whose name begins with _ - that's not good, as all such names are reserved for the implementation. Even worse when enumName begins with a capital letter, as this could collide with macros!


I don't like the single function switching between conversions. Is there really a use-case where we decide which direction to convert at run-time?

Much better to define a pair of functions, one for each direction of conversion - enumName str_to_enumName(const char*) and const char* enumName_to_str(enumName).


This is a standard use case for an X-macro, where we define a macro that can apply another macro to each of the members of the enum:

#include <string.h>
#include <limits.h>
#define X_ENUM_VALUE(v, s) v,
#define X_TO_STR(v, s) case v: return s;
#define X_FROM_STR(v, s) if (!strcmp(x, s)) { return v; }
#define MAKE_ENUM(X, name) \
 \
typedef enum { \
 X(X_ENUM_VALUE) \
} name; \
 \
const char *name##_to_str(name x) \
{ \
 switch(x) { \
 X(X_TO_STR) \
 default: return "ENUM_N/A"; \
 } \
} \
 \
name str_to_##name(const char* x) \
{ \
 X(X_FROM_STR) \
 return UINT_MAX; \
}

Then for each enum, we just need something like

#define ENUM_NUM(DO) \
 DO(num_zero, "Zero") \
 DO(num_one, "One") \
 DO(num_two, "Two") \
MAKE_ENUM(ENUM_NUM, num)

Simple demo of the above:

#include <stdio.h>
int main()
{
 printf("%s\n", num_to_str(num_one));
 printf("%d\n", str_to_num("Two"));
}
answered Aug 23, 2024 at 13:32
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.