constexpr error at compile-time, but no overhead at run-time

Viewed 8023

There is a well-known trick to cause a compile-time error in the evaluation of a constexpr function by doing something like this:

constexpr int f(int x) {
    return (x != 0) ? x : throw std::logic_error("Oh no!");
}

And if the function is used in a constexpr context you will get a compile-time error if x == 0. If the argument to f is not constexpr, however, then it will throw an exception at run time if x == 0, which may not always be desired for performance reasons.

Similar to the theory of assert being guarded by NDEBUG, is there a way to cause a compile-time error with a constexpr function, but not do anything at run time?

Finally, do relaxed constexpr rules in C++1y (C++14) change anything?

6 Answers

This isn't the most beautiful, but seems to work for my (slightly different) use-case. N.B. that I haven't tested it exhaustively, it may well not be perfect and am sharing in case it's of any help. Will happily take feedback:

// Arguments not guaranteed to be compile-time
constexpr bool singleDigitOnlyIsMultipleOfThree(const unsigned number){
    if ((number / 10) > 0)
        throw std::logic_error("ConstexprError");
    switch(number % 3){
        case 0:  return true;
        case 1:  return false;
        case 2:  return false;
        default: throw std::logic_error("Should be unreachable");
    }
}

// Both arguments and return value guaranteed to be compile-time
template<unsigned number>
constexpr bool singleDigitOnlyIsMultipleOfThree(){
    // Because number is constexpr, we guarantee we go through the above implementatio nas constexpr.
    return singleDigitOnlyIsMultipleOfThree(number);
}


int main(){

    constexpr bool   run1 = singleDigitOnlyIsMultipleOfThree(6);     // true
    constexpr bool   run2 = singleDigitOnlyIsMultipleOfThree(7);     // false
    const     bool   run3 = singleDigitOnlyIsMultipleOfThree(100);   // throws

    constexpr bool const1 = singleDigitOnlyIsMultipleOfThree<6>();   // true
    constexpr bool const2 = singleDigitOnlyIsMultipleOfThree<7>();   // false
    constexpr bool const3 = singleDigitOnlyIsMultipleOfThree<100>(); // does not compile

    cout << run1 << " " << run2 << " " << const1 << " " << const2 << endl;
    
}

This funciton should return a (potentially compile-time) value indicating if the argument is divisible by three, but not allow any value >= 10. When called using template arguments, we have a guaranteed constant expression. When using function brackets as a constant expression, we have a compile-time constant. When using function brackets as a runtime expression, then we will have an exception in lieu of a compiler error.

There is the advantage that we can implement the logic using a constexpr function, thus avoiding the need to do it the old-fashioned way using all template specialisation. So our code is faster to write, more succint, and more expressive. However, we can still call it at compile-time and enforce the constant nature of the output and generate a compile-time error. If we want to gamble with runtime values, then it should have behaiour to match. Major disadvantage is having two separate ways to call the function. If we really want to ensure it's a constant expression, then we need to use the angle-bracket syntax, or force assigning to a constexpr.

Related