I am trying to get a string-based switch expression to work in C using a hash function. I've been able to get it to work with clean syntax using 'constexpr' with Clang/LLVM turned to C++, even though the code is C.
However, there are of course odd side effects of having it compile as C++, like lack of void* implicit casting which becomes really awkward.
So the question is how to solve this dilemma (without slapping the C11 committee upside their head for why this wasn't added to the C spec)
Here is my current example code:
constexpr uint64 cHash(char const* text, uint64 last_value = basis)
{
return *str ? cHash(text+1, (*text ^ last_value) * prime) : last_value;
}
void SwitchFunction(char const* text)
{
switch(Hash(text))
{
case cHash("first"):
break;
case cHash("second"):
break;
case cHash("third"):
break;
default:
break;
}
}
The keyword constexpr was introduced in C++11 and improved in C++14. It means constant expression. Like const , it can be applied to variables: A compiler error is raised when any code attempts to modify the value. Unlike const , constexpr can also be applied to functions and class constructors.
#define directives create macro substitution, while constexpr variables are special type of variables. They literally have nothing in common beside the fact that before constexpr (or even const ) variables were available, macros were sometimes used when currently constexpr variable can be used.
constexpr stands for constant expression and is used to specify that a variable or function can be used in a constant expression, an expression that can be evaluated at compile time. The key point of constexpr is that it can be executed at compile time.
static defines the object's lifetime during execution; constexpr specifies that the object should be available during compilation. Compilation and execution are disjoint and discontiguous, both in time and space. So once the program is compiled, constexpr is no longer relevant.
I'm a bit late to the party, but was recently facing the same problem.
For such a simple hash function, you can just implement it using the C preprocessor. The downside is that the preprocessor can not split strings into characters, so instead of hash("first")
you will have to write HASH('f','i','r','s','t')
. The HASH
macro is implemented using __VA_ARGS__
and works for strings with up to eight characters.
I have also turned you hash function from a recursive one into an iterative one, which is a bit easier to read and doesn't require the optional argument. The generated assembly is pretty much the same (https://godbolt.org/z/1g8LPI).
#include <stdio.h>
typedef unsigned long uint64;
#define HASH_BASIS 17UL
#define HASH_PRIME 11UL
#define HASH_1(ARG1) ((ARG1 ^ HASH_BASIS) * HASH_PRIME)
#define HASH_2(ARG1, ARG2) ((ARG2 ^ HASH_1(ARG1)) * HASH_PRIME)
#define HASH_3(ARG1, ARG2, ARG3) ((ARG3 ^ HASH_2(ARG1, ARG2)) * HASH_PRIME)
#define HASH_4(ARG1, ARG2, ARG3, ARG4) \
((ARG4 ^ HASH_3(ARG1, ARG2, ARG3)) * HASH_PRIME)
#define HASH_5(ARG1, ARG2, ARG3, ARG4, ARG5) \
((ARG5 ^ HASH_4(ARG1, ARG2, ARG3, ARG4)) * HASH_PRIME)
#define HASH_6(ARG1, ARG2, ARG3, ARG4, ARG5, ARG6) \
((ARG6 ^ HASH_5(ARG1, ARG2, ARG3, ARG4, ARG5)) * HASH_PRIME)
#define HASH_7(ARG1, ARG2, ARG3, ARG4, ARG5, ARG6, ARG7) \
((ARG7 ^ HASH_6(ARG1, ARG2, ARG3, ARG4, ARG5, ARG6)) * HASH_PRIME)
#define HASH_8(ARG1, ARG2, ARG3, ARG4, ARG5, ARG6, ARG7, ARG8) \
((ARG8 ^ HASH_7(ARG1, ARG2, ARG3, ARG4, ARG5, ARG6, ARG7)) * HASH_PRIME)
#define HASH_COUNT(ARG1, ARG2, ARG3, ARG4, ARG5, ARG6, ARG7, ARG8, func, ...) \
func
#define HASH(...) \
HASH_COUNT(__VA_ARGS__, HASH_8(__VA_ARGS__), HASH_7(__VA_ARGS__), \
HASH_6(__VA_ARGS__), HASH_5(__VA_ARGS__), HASH_4(__VA_ARGS__), \
HASH_3(__VA_ARGS__), HASH_2(__VA_ARGS__), HASH_1(__VA_ARGS__))
uint64 hash(const char *text) {
uint64 h = HASH_BASIS;
char c;
while ((c = *text++) != '\0') {
h = (c ^ h) * HASH_PRIME;
}
return h;
}
int main(int argc, char *argv[]) {
const char *text = argc > 1 ? argv[1] : "";
switch (hash(text)) {
case HASH('f', 'i', 'r', 's', 't'):
puts(text);
break;
case HASH('s', 'e', 'c', 'o', 'n', 'd'):
puts(text);
break;
case HASH('t', 'h', 'i', 'r', 'd'):
puts(text);
break;
default:
puts("oops");
break;
}
}
Is there a way to get constexpr option turned on with C?
No, no such thing exists in C.
Is there a way to get implicit void* casting turned on with C++?
No, C++ has mandatory type safety of pointers.
Is there another clean way to code this in C11/C99 that doesn't require recalculating hashes?
The only way you can do it, is the traditional way with macros. In case you create a function-like macro with those parameters, and only use it on compile-time constants, then all computations will be done at compile-time. Unfortunately, the code will turn rather ugly, but there is no way to avoid that in C.
The best way might be to prepare all such compile-time parameters with an external script/program, then just store them as raw data tables in the C program.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With