Rahul Iyer
Rahul Iyer

Reputation: 21025

How to convert a string to a constant integer?

I have strings which represent image names like "foobar.png" etc. As you know, switch-case in C++ does not support switching on a string.

I'm trying to work around this, by hashing the string to std::size_t, and then using that value in the switch-case statements.

For example:

        //frameName is an std::string which represents foobar.png etc..
        switch (shs(frameName)) { //shs is my hash func which returns std::size_t;
            case shs(Pfn::fs1x1): //Problem in this line
            default:{
                break;
            }
        }

In a separate file (Pfn.hpp):

namespace Pfn{ const std::string fs1x1 = "fs1x1"; };

The problem is, that in my case statement the compiler reports that shs(Pfn::fs1x1) is not a constant expression. The exact error message is:

Case value is not a constant expression:

It would be really tedious to work out all the hash-values in advance and then hardcode them into the case statements. Do you have a suggestion on how I can somehow create the constant expressions at runtime ?

EDIT: My shs function:

static std::size_t shs(std::string string){
    return Hash::shs::hs(string);
}

//...

namespace Hash{
    struct shs{
    public:
        inline std::size_t operator()(const std::string &string)const{
            return hashString(string);
        }

        static std::size_t hs(const std::string &string){
            std::size_t seed = 0;
            hash_combine(seed,string);
            return seed;
        }

        //From Boost::hash_combine.
        template <class T>
        static inline void hash_combine(std::size_t& seed, const T& v)
        {
            std::hash<T> hasher;
            seed ^= hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2);
        };
    };
}

Upvotes: 1

Views: 763

Answers (3)

krzaq
krzaq

Reputation: 16431

shs's argument needs to be constexpr and shs itself must be constexpr as well. Chances are, you might want to provide different implementations for the compile-time version and the run-time version of the hash, due to C++11 constraints on constexpr functions.

I wrote a post on this very topic some time ago, using fnv1a as the hash algorithm. Here are the important parts:

constants:

typedef std::uint64_t hash_t;

constexpr hash_t prime = 0x100000001B3ull;
constexpr hash_t basis = 0xCBF29CE484222325ull;

Runtime hash:

hash_t hash(char const* str)
{
    hash_t ret{basis};

    while(*str){
        ret ^= *str;
        ret *= prime;
        str++;
    }

    return ret;
}

Compile-time hash:

constexpr hash_t hash_compile_time(char const* str, hash_t last_value = basis)
{
    return *str ? hash_compile_time(str+1, (*str ^ last_value) * prime) : last_value;
}

user defined string literal:

constexpr unsigned long long operator "" _hash(char const* p, size_t)
{
    return hash_compile_time(p);
}

and usage:

switch(fnv1a_64::hash(str)){
case "first"_hash:
    cout << "1st one" << endl;
    break;
case "second"_hash:
    cout << "2nd one" << endl;
    break;
case "third"_hash:
    cout << "3rd one" << endl;
    break;
default:
    cout << "Default..." << endl;
}

demo

But please, think of the children! Unless you can guarantee that there will be no hash collisions, this is playing with fire and is not fit to be production code.

Upvotes: 3

tristan
tristan

Reputation: 4322

By definition, constant expression means an expression that can be evaluated at compile time.

But you can use case func(): if func() is constexpr.

Upvotes: 1

Jim B.
Jim B.

Reputation: 4714

I'll assume your hash won't have any collisions for purposes of this answer.

If you instead define your hash as a preprocessor function, you can create constants you can match to.

This post might help: Compile-time (preprocessor) hashing of string

Upvotes: 2

Related Questions