有一个众所周知的技巧,通过执行以下操作来评估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
规则会改变什么吗?
答案 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 );
}