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

送分小仙女□ 提交于 2019-11-28 10:43:15

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.

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

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.

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.

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 );
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!