Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

constexpr in C (or equivalent)

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)

  1. Is there a way to get constexpr option turned on with C?
  2. Is there a way to get implicit void* casting turned on with C++?
  3. Is there another clean way to code this in C11/C99 that doesn't require recalculating hashes?

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;
    }
}
like image 850
Troy Harvey Avatar asked Apr 09 '14 08:04

Troy Harvey


People also ask

Does constexpr exist in C?

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.

Is constexpr same as #define?

#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.

What does constexpr mean?

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.

What is constexpr static?

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.


2 Answers

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;
    }
}
like image 200
Henri Menke Avatar answered Sep 21 '22 09:09

Henri Menke


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.

like image 35
Lundin Avatar answered Sep 20 '22 09:09

Lundin