Friday, April 13, 2012

C++11 string switch

C++11 added a slew of new features - lambda, auto, uniform initilizers, move semantics - the list is large. Near the bottom, almost forgotten, is general const expressions. C++11 adds the new keyword constexpr (n2335), which at first glance seems like just an esoteric tightening of const, but actually is so much more.  C++ has always had the constant expressions, so explicitly labelling something as such would seem to not gain much. But au contraire, constexpr is an incredibly useful addition that will greatly simplify meta programming in C++.

What can you do with constexpr that you can't you do with templates? Turns out, lots:

You can generate a compile time string hash (string hashing). Perform all sorts of compile time string manipulation (constexprstr). Do compile time unit conversion, and generate philosphical debate (turing).

constexpr can also interact with other C++11 features in interesting ways. For example using string hashing and user literals it is possible to do a very good approximation of a string switch statement.

Lets start with a compile time string hasher, like the one found here.

// FNV-1a constants
static constexpr unsigned long long basis = 14695981039346656037ULL;
static constexpr unsigned long long prime = 1099511628211ULL;

// compile-time hash helper function
constexpr unsigned long long hash_one(char c, const char* remain, unsigned long long value)
{
    return c == 0 ? value : hash_one(remain[0], remain + 1, (value ^ c) * prime);
}

// compile-time hash
constexpr unsigned long long hash_(const char* str)
{
    return hash_one(str[0], str + 1, basis);
}

// run-time hash
unsigned long long hash_rt(const char* str) {
    unsigned long long hash = basis;
    while (*str != 0) {
        hash ^= str[0];
        hash *= prime;
        ++str;
    }
    return hash;
}
constexpr long long operator"" _hash( const char* str, size_t n ) {
    return hash_( str );
}

Now we can use the _hash string literal to generate a compile time string hash, which can be used anywhere a constant expression can be, such in in non-type template parameters and switch statements!
template < long long n >
struct some_template {
    static const long long value = n;
};

void print( const char * str ) {
    switch ( hash_( str ) ) {
       case  "hello"_hash: std::cout << "hello string passed\n"; break;
       case  "world"_hash: std::cout << "world string passed\n"; break;
    };
}

int main() {

    long long hash = some_template< "hello"_hash >::value;

    print( "hello" );
    print( "world" );

    return 0;
}

Of course it's not really limited to strings either, the technique can be extended to any type that has a hash and constexpr constructor.

No comments:

Post a Comment