athos athos - 3 months ago 20
C++ Question

Why force constexpr have compile time error only?

I'm watching CppCon 2015: Scott Schurr “constexpr: Applications", on 19'28" he's showing a trick to force compile time error only:


A Way to Force Compile-Time Only?


  • Not within the standard

  • But a hack that sometimes works



Unresolved Symbol In Throw

extern const char* compile11_bin_invoked_at_runtime;
template <typename T = std::uint32_t>
constexpr T compile11_bin(
constexpr_txt t,
std::size_t i = 0, // index
std::size_t b = 0, // bit count
T x = 0) // accumulator
{
return
i >= t.size() ? x : // end recursion
b >= std::numeric_limits<T>::digits ?
throw std::overflow_error("Too many bits!") :
t[i] == ',' ? compile11_bin<T>(t, i+1, b, x) :
t[i] == '0' ? compile11_bin<T>(t, i+1, b+1, (x*2)+0) :
t[i] == '1' ? compile11_bin<T>(t, i+1, b+1, (x*2)+1) :
throw std::domain_error( // Only '0', '1', and ','
compile11_bin_invoked_at_runtime);
}



I'm curious here, what's the motivation to force a constexpr to have compile time error only?

Answer

Compile time errors are always better than runtime errors.

Why? Because compile time errors can be fixed without running the application, and can thus be easily fixed. By throwing an unresolved external symbol, you can force the result of compile11_bin to be stored in a constexpr variable, which enables you to detect errors more quickly.

The example from the slide is:

constexpr auto maskA = constexpr11_bin<std::uint8_t>("1110 0000");
auto maskB = constexpr11_bin<std::uint8_t>("0001 1111");

Here, maskA results in a compile time error, which can be easily fixed. maskB on the other hand results in a runtime error, as maskB is not constexpr, and so the result doesn't have to be evaluated at compile-time, and you'll get a runtime error.

That's because compile11_bin_invoked_at_runtime is a unresolved external symbol, and every symbol has to have a definition, and so you get an error at compile-time, even if it's not the compiler per se that complains.