7 ms·
Header only implementations of data structures are definitely the only way to accomplish type-safe generics in C, which is a great reason to avoid C! It's part
by efficax 6y ago
Header only implementations of data structures are definitely the only way to accomplish type-safe generics in C, which is a great reason to avoid C! It's part of the reason why all C code bases eventually become a kind of unique, macro filled language of their own once they grow complex enough. It's not enough to know C to start working on this code, you have to know all the ins and outs of the weird macro expansions that form the boilerplate patterns and are unique to this codebase only.
Apart from being a pain in the ass to write (don't forget your escape slashes (\) for line endings!), using these macros makes debugging much harder - you no longer have a stack trace (although GDB is fairly smart about this, it's still less than ideal).
- tediousdemise 6y agoTo add - the preprocessor is a pretty dated thing that merely uses text substitution, so you have no easy way to debug the code that the preprocessor passes to the compiler. Defines such as function-like macros throw away type safety and can have extremely unexpected behavior. For example, you might think this code will do what you want. #define SQ(x) x*x cout << SQ(2) // prints 4 since 2*2 = 4 cout << SQ(1+3) // prints 7 since 1+3*1+3 = 7 Macros are pretty much entirely unnecessary and should be avoided. Compilers inline nowadays, so you don’t really gain anything from using a macro when you could have just made a function that gives you type safety and intuitive behavior. If you are trying to define constants, use const, enum, or enum classes. If you are trying to define multiple versions of the same function, use polymorphism or templates. If you are trying to wrap around arbitrary code, use lambdas.
- monocasa 6y agoI agree that you shouldn't be doing this stuff in production if you don't know the ends and outs of doing this idiomatically. For instance, the lack of parenthesis ala #define SQ(x) ((x)*(x)) set off my C spidey senses. I use them even for constants in pound defines just to be consistent. That being said, there are benefits, like the type safety listed being one of them. No weird cast to void* and go to town on raw memory shenanigans you would have to do to use functions in intrusive collections in C.
- mgerdts 6y agoYou mean: #define SQ(x) ((x)*(x))
- deleted 6y ago[deleted]
- monocasa 6y agoYeah, thanks. That's what I get for trying to type code on my phone.
- throwaway3neu94 6y agoThere's another pitfall: it evaluates a parameter twice. Imagine calling SQ(func_with_side_effects()). If you can assume a compiler with statement expressions and typeof (gcc, clang, probably icc in this case): #define SQ(x) ({ typeof(x) x_ = (x); x_ * x_ }) If not (MSVC++) then best of luck. Anyway I 100% agree people way, way overuse macros. Very often a static inline function is a better trade-off.
- anitil 6y agoI've started moving to using python to generate what I need a lot of the time now in C. I'll either make a '.h' file, read it in and generate the boilerplate I need, or just do it using native python and emitting the C I need. I'm never again hand-writing an my_enum_to_str routine.
- Galanwe 6y agoI share your feelings, though some years ago I discovered the neat trick of "xmacros". In a nutshell: #define COLOR_LIST \ X( RED ) \ X( GREEN ) \ X( BLUE ) \ X( PURPLE ) enum Color { #define X(code) code, COLOR_LIST #undef X }; static const char* colorNames[] = { #define X(code) #code, COLOR_LIST #undef X }; You can even make it cleaner and store more metadata: // --- color.def X( RED, "Red", 0xFF0000 ) X( GREEN, "Green", 0x00FF00 ) X( BLUE, "Blue", 0x0000FF ) // --- color.h enum Color { #define X(code,name,mask) code, # include "color.def" #endif }; static const char* colorNames[] = { #define X(code,name,mask) name, # include "color.def" #undef X }; static unsigned int colorMasks[] = { #define X(code,name,mask) mask, # include "color.def" #undef X };
- tempodox 6y agoYep, good old trick. Just don't forget to #undef X when you're done.