5
votes

recently I've tried to take advantage of C++0x constexpr under MSVC 2015 and my objective was to achieve compile-time hash strings. I wrote a simple FNV-1a hash algorithm as a constexpr function using, as required, a single return statement (ternary operator) and calling only constexpr functions, here it is:

template <size_t N>
constexpr U32 StringID_FNV1a_32(const char(&str)[N], I32 charIndex = 0, U32 hash = 2166136261U)
{
    return charIndex < N-1 ? StringID_FNV1a_32(str, charIndex +1, (hash ^ str[charIndex]) * 16777619U) : hash;
}

I also made a little macro to be able to change the algorithm under the hood without any trouble:

#define STRING_ID(str)  core::utility::StringID_FNV1a_32(str)

then I used this macro in my code, carefully checking if any breakpoint was hit and, also, the generated assembly code. Here's the little scenario:

//1. normal variable test
U32 hash1 = STRING_ID("abc");  

//2. enum test
enum {    
    hash2 = STRING_ID("abc")
};

//3. constexpr variable test
constexpr U32 hash3 = STRING_ID("abc");

And here the facts:

  1. first test was called at run time
  2. second test was performed at compile time
  3. third test was called at run time

As you can imagine I'm a little confused about the first and the third attempt.

Why in the third scenario is the compiler allowed to call the function at runtime? even though the msdn says clearly "The primary difference between const and constexpr variables is that the initialization of a const variable can be deferred until run time whereas a constexpr variable must be initialized at compile time." [https://msdn.microsoft.com/it-it/library/dn956974.aspx#Anchor_3]

Can be related to the fact that I'm in debug mode with all the optimizations turned off? and what about the first test?, is there any way to force the compiler to perform the hash at compile time?

1
what happens if you make "abc" a constexpr string and then pass that to the function? - johnbakers
If you use hash3 in a constexpr context, say static_assert(hash3 == hash2, "!"); you'll see that correct value is already available at compile time. Yet for some reason, it is also computed and initialized again at runtime, probably a bug. - melak47
@melak47 you are right about the static_assert test... quite strange... maybe it's really a bug... - elven_inside
@johnbakers use a constexpr char str[4] as argument doesn't change the run-timeness - elven_inside

1 Answers

1
votes

MSVC's behavior can be quite strange, however it is possible to force it to make constexpr functions run at compile time.

#define COMPILE_TIME(value) ((decltype(value))CompileTime<decltype(value), value>::ValueHolder::VALUE)

template<typename T, T Value>
struct CompileTime
{
    enum class ValueHolder : T
    {
        VALUE = Value
    };
};

This forces the value to be passed as a template argument + an enumeration value, thus making it strictly compile-time only.
Also please note that this works only for integer types.

You can use it simply by putting the call to the constexpr function as a parameter to the COMPILE_TIME macro:

constexpr U32 hash = COMPILE_TIME(STRING_ID("abc"));