31

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?

David Stone
  • 26,872
  • 14
  • 68
  • 84
  • I would have recommended using a *meta-function*, but since you are asking specifically for a resolution through a `constexpr` function, go on. Good question. – Max Truxa Dec 09 '13 at 00:38
  • 3
    What should the function do instead of throwing an exception? – uk4321 Dec 09 '13 at 00:39
  • Here is a 33 page paper that discusses this issue in detail. http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3583.pdf – KeithSmith Dec 09 '13 at 00:48
  • 1
    @uk4321: Nothing. The runtime equivalent of just `return x` in my example. – David Stone Dec 09 '13 at 00:57
  • 6
    Did you have something like this is mind: `constexpr int f(int x) { return (x != 0) ? x : reinterpret_cast(x); }`? It does a no-op if `x == 0`, but a `reintepret_cast` is not allowed in a constant expression (at least in C++11), therefore it will cause a compile-time error in the same way the throw-expression does. An even simpler way would be to use a function `g` which is no `constexpr` and returns the value for `x == 0`: `return (x!=0)?x:g(x);` – dyp Dec 09 '13 at 01:05
  • @DyP are you sure that works? I tried it and it never errors, maybe I did it wrong, or maybe gcc 4.8 is not correct: http://ideone.com/2iozle – uk4321 Dec 09 '13 at 01:14
  • @uk4321 Might be a g++ extension. clang++3.4 refuses it; also see 5.19/2, one of the last bullet points disallows using a `reinterpret_cast` in a constant expression. – dyp Dec 09 '13 at 01:16
  • @uk4321 Using the non-constexpr function variant (`constexpr int f(int x) { return (x != 0) ? x : g(x); }`, where `g` is a non-`constexpr` function) "works" also for g++4.8, in the sense that `constexpr auto x = f(0);` causes a compile-time error. – dyp Dec 09 '13 at 01:31
  • 1
    @DyP I would imagine that doesn't work if you don't assign the result to a `constexpr` variable, correct? I don't know if the OP was going to use it only in such scenarios. – uk4321 Dec 09 '13 at 01:36
  • The actual use case for this is in a constructor. I can accept that the checking won't be perfect, I'm just trying to do better than I am right now. My current situation is that I just declare something to be undefined behavior (and give users an alternative that does a runtime check when needed via `throw`). I'd like to do a compile-time check whenever I can, and I accept that in general you have to declare a variable `constexpr` for maximum static analysis. – David Stone Dec 09 '13 at 01:42
  • @dyp compiles fine with clang 9.0.0 either. Also, the code OP posted does not throw in compile time too: neither with GCC nor with Clang. – Hi-Angel Nov 01 '19 at 13:35

6 Answers6

18

Is there a way to cause a compile-time error with a constexpr function, but not do anything at run time?

You can use the exact same trick, but instead of using a throw-expression, use an expression that is not a constant expression but does what you want at runtime. For instance:

int runtime_fallback(int x) { return x; } // note, not constexpr
constexpr int f(int x) {
  return (x != 0) ? x : runtime_fallback(0);
}

constexpr int k1 = f(1); // ok
constexpr int k2 = f(0); // error, can't call 'runtime_fallback' in constant expression
int k3 = f(0);           // ok

Do relaxed constexpr rules in C++1y (C++14) change anything?

Not in this area, no. There are some forms of expression that are valid in constant expressions in C++14 but not in C++11, but neither throw-expressions nor calls to non-constexpr functions are on that list.

Richard Smith
  • 13,696
  • 56
  • 78
3

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.

A function argument is never considered to be a constant expression. The distinction would require compile-time and runtime objects to have different types.

Even though the compiler is using pure functional semantics when it evaluates the function at compile time, it's still the same function with the same meaning. If you want another function of similar but different meaning, you will have to either define another entire function, or make a template.

You could use a signature like this:

template< typename int_type >
constexpr int f(int_type x);

with calls like this:

f( std::integral_constant< int, 0 >() ) // Error.
f( std::integral_constant< int, 3 >() ) // OK.
f( 0 ) // Not checked.

Metaprogramming can tell that integral_constant means a compile-time value. But I don't think it's really appropriate. If one sense of the function works with zero and the other doesn't, then you have two different functions.

A wrapper idiom could prevent duplication among the different functions:

constexpr int f_impl(int x) { // Actual guts of the function.
    return x;
}

int f(int x) { // Non-constexpr wrapper prevents accidental compile-time use.
    assert ( x != 0 && "Zero not allowed!" );
    return f_impl( x );
}

template< int x > // This overload handles explicitly compile-time values.
constexpr int f( std::integral_constant< int, x > ) {
    static_assert ( x != 0, "Zero not allowed!" );
    return f_impl( x );
}
Potatoswatter
  • 134,909
  • 25
  • 265
  • 421
2

This should work:

#ifdef NDEBUG
    // Suppresses unused variable warnings in release builds.
    #define ASSERT(X) (void(sizeof (X)))
#else
    #define ASSERT(X) ((X) ? void() : std::abort())
#endif

constexpr int f(int const x)
{
    return ASSERT(x != 0), x;
}

You can see the output here. If you add constexpr to the start of line 17 then you'll get a compile-time error instead.

Simple
  • 13,992
  • 2
  • 47
  • 47
1

Instead of using a constexpr function, you should use static_assert. This lets you run an assertion at compile-time that has zero runtime cost.

Lily Ballard
  • 182,031
  • 33
  • 381
  • 347
0

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.

stellarpower
  • 332
  • 3
  • 13
-1

This seems to do the trick. It is not very pretty, but the idea is to distinguish value that is available at compile time from one that isn't using SFINAE on constexprs. I can compile this with clang 3.3 and have the compilation fail when I try to use f(0) in a constexpr context, but not throw when I use it at runtime. You can probably create a one-parameter maybethrow overload that makes the (not_)preferred trick internal to its implementation.

struct not_preferred {};
struct preferred { operator not_preferred() { return not_preferred(); } };

template< typename T, T X >
T compiletime() { return X; }

template< typename T >
constexpr auto maybethrow( preferred, T x ) -> decltype( compiletime< T, x >() )
{
    return 0 ? x : throw 1;
}

template< typename T >
constexpr auto maybethrow( not_preferred, T x ) -> T
{
    return x;
}

constexpr int f(int x)
{
    return x ? x + 1 : maybethrow( preferred(), x + 1 );
}
mornfall
  • 428
  • 2
  • 6
  • 3
    A function parameter is never assumed to be a *constant expression*. Therefore I think the first overload of `maybethrow` is ill-formed (even though an early clang++3.4 accepts it). – dyp Dec 09 '13 at 01:19
  • @DyP You are right, I don't know what I was thinking. I'm also quite confused as to why would it work. I must have invoked something unintentionally, or my test code is bogus. The non-constexpr subroutine trick is much better anyway. – mornfall Dec 09 '13 at 01:25