Compile time computations
We have seen how compile time computations were conducted using templates before C++11. One disadvantage of all template techniques is the bloated syntax. Another is the impossibly long and unhelpful error messages typically produced by compilers. This made templates somehow inaccessible for non-expert C++ programmers. In C++11, an entirely new concept was introduced to make compile time computation accessible for everyone—the constexpr functions, as shown in the following example:
constexpr long long factorial(int n)
{
// C++11 constexpr funct use recursion instead of iteration
// return n <= 1 ? 1 : (n * factorial(n - 1));
// C++14 constexpr funct can use variables and loops
if (n <=1 )
return 1;
long long result = 1;
for (int i = 1; i <= n; ++i)
{
result *= i;
}
return result;
}
As may be seen, we can achieve the same effect as with template metaprogramming, but don't have to agonize about convoluted, unreadable, and verbose syntax. In the C++11 standard, there were quite a few restrictions to constexpr functions, but they were lifted by and by in the later editions of the standard.
Additionally, we can use constexpr functions in conjunction with another new C++11 feature, namely user-defined literals, and let the compiler parse them in compile time, as shown in this example:
constexpr std::uint64_t
constexprHash(char const* str, std::uint64_t lastValue = hashBasis)
{
return *str
? constexprHash(str + 1, (*str ^ lastValue) * hashPrime)
: lastValue;
}
constexpr unsigned long long operator "" _hash(char const* p, size_t)
{
return constexprHash(p);
}
// test:
auto hash = "fiuma di fiumi"_hash;
This example defines a user literal operator _hash() computing a simple hash for a given string. Check out in Compiler Explorer (which will be introduced in the next section) whether the computations for both the previous examples are really performed in compile time!