编译时constexpr错误,但在运行时没有开销

时间:2013-12-09 00:29:28

标签: c++ c++11 constexpr c++14

有一个众所周知的技巧,通过执行以下操作来评估constexpr函数时会导致编译时错误:

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

如果在constexpr上下文中使用该函数,则在x == 0时会出现编译时错误。但是,如果f的参数不是constexpr,那么它将在运行时抛出异常x == 0,由于性能原因,这可能并不总是需要。

assert保护NDEBUG的理论类似,有没有办法在constexpr函数中导致编译时错误,但在运行时没有做任何事情?

最后,C ++ 1y(C ++ 14)中的轻松constexpr规则会改变什么吗?

5 个答案:

答案 0 :(得分:14)

有没有办法在constexpr函数中导致编译时错误,但在运行时没有做任何事情?

您可以使用完全相同的技巧,但不使用 throw-expression ,而是使用不是常量表达式但在运行时执行所需操作的表达式。例如:

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

C ++ 1y(C ++ 14)中的轻松constexpr规则会改变什么吗?

不在这个区域,没有。有些表达式在C ++ 14中的常量表达式中有效,但在C ++ 11中没有,但 throw-expressions 和非constexpr函数的调用都没有那份清单。

答案 1 :(得分:3)

  

但是,如果f的参数不是constexpr,那么它将在运行时抛出异常x == 0,由于性能原因,这可能并不总是需要。

函数参数永远不会被视为常量表达式。这种区别要求编译时和运行时对象具有不同的类型。

即使编译器在编译时评估函数时使用纯函数语义,它仍然是具有相同含义的相同函数。如果您想要另一个具有相似但不同含义的函数,则必须定义另一个完整函数或创建模板。

你可以使用这样的签名:

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

通过这样的电话:

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

元编程可以告诉integral_constant表示编译时值。但我认为这不合适。如果一个函数的意义使用零而另一个没有,那么你有两个不同的函数。

包装器习惯用法可以防止不同功能之间的重复:

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

答案 2 :(得分:1)

您应该使用static_assert,而不是使用constexpr功能。这使您可以在编译时运行一个运行时成本为零的断言。

答案 3 :(得分:1)

这应该有效:

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

您可以看到输出here。如果将constexpr添加到第17行的开头,那么您将收到编译时错误。

答案 4 :(得分:-1)

这个似乎来做这个伎俩。它不是很漂亮,但其目的是区分编译时可用的值和不使用SFINAE的值。我可以使用clang 3.3编译它,并且当我尝试在constexpr上下文中使用f(0)时编译失败,但是当我在运行时使用它时不会抛出。您可以创建一个单参数maybethrow重载,使(not_)首选技巧在其实现内部。

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