簡體   English   中英

編譯時出現 constexpr 錯誤,但運行時沒有開銷

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

有一個眾所周知的技巧可以通過執行以下操作在評估constexpr函數時導致編譯時錯誤:

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

如果在constexpr上下文中使用該函數,如果x == 0您將收到編譯時錯誤。 但是,如果f的參數不是constexpr ,那么如果x == 0 ,它將在運行時拋出異常,出於性能原因,這可能並不總是需要的。

類似於assertNDEBUG保護的理論,有沒有辦法用constexpr函數導致編譯時錯誤,但在運行時不做任何事情?

最后,C++1y (C++14) 中寬松的constexpr規則會改變什么嗎?

有沒有辦法使用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-expression和非constexpr函數的調用都不在該列表中。

但是,如果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 );
}

不應使用constexpr函數,而應使用static_assert 這使您可以在編譯時運行一個運行時成本為零的斷言。

這應該工作:

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

你可以在這里看到輸出。 如果將constexpr添加到第17行的開頭,那么您將收到編譯時錯誤。

這不是最漂亮的,但似乎適用於我的(略有不同的)用例。 注意,我還沒有對它進行詳盡的測試,它可能並不完美,如果有幫助,我會分享。 很樂意接受反饋:

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

這個函數應該返回一個(可能是編譯時)值,指示參數是否可以被 3 整除,但不允許任何值 >= 10。當使用模板參數調用時,我們有一個有保證的常量表達式。 當使用函數括號作為常量表達式時,我們有一個編譯時常量。 當使用函數括號作為運行時表達式時,我們將有一個異常代替編譯器錯誤。

優點是我們可以使用 constexpr 函數來實現邏輯,從而避免需要使用所有模板特化以老式方式來實現。 所以我們的代碼寫得更快,更簡潔,也更有表現力。 但是,我們仍然可以在編譯時調用它並強制輸出的常量性質並生成編譯時錯誤。 如果我們想用運行時值來賭博,那么它應該有匹配的行為。 主要缺點是有兩種不同的方法來調用該函數。 如果我們真的想確保它是一個常量表達式,那么我們需要使用尖括號語法,或者強制分配給一個 constexpr。

似乎可以解決問題。 它不是很漂亮,但其目的是區分編譯時可用的值和不使用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 );
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM