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 );
}