简体   繁体   English

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

[英]constexpr error at compile-time, but no overhead at run-time

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函数时导致编译时错误:

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 .如果在constexpr上下文中使用该函数,如果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.但是,如果f的参数不是constexpr ,那么如果x == 0 ,它将在运行时抛出异常,出于性能原因,这可能并不总是需要的。

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?类似于assertNDEBUG保护的理论,有没有办法用constexpr函数导致编译时错误,但在运行时不做任何事情?

Finally, do relaxed constexpr rules in C++1y (C++14) change anything?最后,C++1y (C++14) 中宽松的constexpr规则会改变什么吗?

Is there a way to cause a compile-time error with a constexpr function, but not do anything at run time? 有没有办法使用constexpr函数导致编译时错误,但在运行时没有做任何事情?

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. 您可以使用完全相同的技巧,但不使用throw-expression ,而是使用不是常量表达式但在运行时执行所需操作的表达式。 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? C ++ 1y(C ++ 14)中的轻松constexpr规则会改变什么吗?

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. 有些表达式在C ++ 14中的常量表达式中有效,但在C ++ 11中没有,但是throw-expression和非constexpr函数的调用都不在该列表中。

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. 但是,如果f的参数不是constexpr ,那么如果x == 0 ,它将在运行时抛出异常,出于性能原因,这可能并不总是需要。

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. 元编程可以告诉integral_constant意味着编译时值。 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 . 不应使用constexpr函数,而应使用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. 如果将constexpr添加到第17行的开头,那么您将收到编译时错误。

This isn't the most beautiful, but seems to work for my (slightly different) use-case.这不是最漂亮的,但似乎适用于我的(略有不同的)用例。 NB 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.这个函数应该返回一个(可能是编译时)值,指示参数是否可以被 3 整除,但不允许任何值 >= 10。当使用模板参数调用时,我们有一个有保证的常量表达式。 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.优点是我们可以使用 constexpr 函数来实现逻辑,从而避免需要使用所有模板特化以老式方式来实现。 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.如果我们真的想确保它是一个常量表达式,那么我们需要使用尖括号语法,或者强制分配给一个 constexpr。

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. 它不是很漂亮,但其目的是区分编译时可用的值和不使用SFINAE的值。 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. 我可以使用clang 3.3编译它,并且当我尝试在constexpr上下文中使用f(0)时编译失败,但是当我在运行时使用它时不会抛出。 You can probably create a one-parameter maybethrow overload that makes the (not_)preferred trick internal to its implementation. 您可以创建一个单参数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 );
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM