I have a constexpr
function that computes CRC32 hash from string literal.
template <size_t len>
constexpr uint32_t ctcrc32(const char (&str)[len]) {
return detail::crc32<len - 2>(str) ^ 0xFFFFFFFF;
}
(it refers to other constexpr
functions)
What I want to do is to call some other function that accepts uint32_t
value and uses it to access data in some unordered_map
. Such call looks like this:
uniformByNameCRC32(ctcrc32("uPointLight.position"));
I expect that "uPointLight.position"
's hash computes once at build time and then a resulting constant is passed to uniformByNameCRC32()
, but that is not the case and ctcrc32()
is called at runtime which kills CPU basically, since I have a lot of uniformByNameCRC32()
calls.
This, however, works fine:
std::array<uint64_t, ctcrc32("string_literal")> array;
Such code compiles and indicates that ctcrc32()
's return value is indeed a constexpr
.
What am I missing here?
A constexpr function is a function that can be invoked within a constant expression. A constexpr function must satisfy the following conditions: It is not virtual. Its return type is a literal type.
We allow annotating a function parameter with constexpr with the same meaning as a variable declaration: must be initialized with a constant expression.
#define (also called a 'macro') is simply a text substitution that happens during preprocessor phase, before the actual compiler. And it is obviously not typed. constexpr on the other hand, happens during actual parsing. And it is indeed typed.
A const int var can be dynamically set to a value at runtime and once it is set to that value, it can no longer be changed. A constexpr int var cannot be dynamically set at runtime, but rather, at compile time. And once it is set to that value, it can no longer be changed.
The OP ask (in a comment)
how to wrap it in some macro since I don't want to write two lines of code each time
I suppose you can use a function that receive the ctcrc32
value as template value and simply return it.
I mean
template <uint32_t N>
constexpr uint32_t getCV () // get constexpr value
{ return N; }
that you can use as follows
uniformByNameCRC32(getCV<ctcrc32("uPointLight.position")>());
Passing the ctcrc32()
value to getCV()
as template parameter force the compiler to calculate it compile time.
There are no guarantees anything is done at compile time vs run time in C++. C++ in theory permits your code to be passed as a string literal to a C++ interpreter at runtime. (There are some mandatory diagnostics for some ill-formed programs, but the form of said diagnostics is not specified, and you are permitted to emit a diagnostic even if there is no ill-formedness, so simply printing out a line saying "this code will be compiled later" satisified the standard).
constexpr
simply lets you do some things in code that traditionally are done at compile time, like size arrays on the stack or use constants in the name of a type.
There are zero major C++ compilers which name new types at runtime. So, we have an in to force something to run at compile time; in c++14 we have template constants:
template<uint32_t v>
std::integral_constant< uint32_t, v > kint32{};
With that, we can do:
uniformByNameCRC32(kint32<ctcrc32("uPointLight.position")>);
should execute ctcrc32
at compile time. Not doing so would require a lot of work by the compiler.
In c++17 you can even do:
template<auto x>
std::integral_constant< std::decay_t<decltype(x)>, x > k{};
and it works for any type.
std::integral_constant
in turn implicitly converts back to a value of the same type.
Use intermediate constrexpr variable:
constexpr auto value = ctcrc32("uPointLight.position")
uniformByNameCRC32(value);
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