2010-08-03 18 views
0

下面是一個示例setup ...宏或模板CHECKEXPR_RETURNVAL(EXPR,VAL),它在返回VAL時檢查EXPR是否爲TRUE。如何讓C++創建一個使用編譯時檢查常量和斷言變量的表達式?

這是一個不同的地方有用的 - 就像這個高度簡化的例子:

#define ISPOW2(VAL)   ((0!=VAL)&&(0==(VAL&(VAL-1)))) 
#define _ALIGNPOW2(VAL,ALIGN) ((VAL+(ALIGN-1))&(~(ALIGN-1))) 

#define ALIGNPOW2(VAL,ALIGN) CHECKEXPR_RETURNVAL(\ 
    ISPOW2(ALIGN) , _ALIGNPOW2(VAL,ALIGN)) 

所以,難的是這樣的:我想要做的,如果可能的編譯時檢查,如果該值不是在編譯時確定的常量,然後執行運行時檢查。

基本上,這個想法是儘快捕捉不好的參數;如果你可以在編譯時捕獲一個壞的參數,那比在運行時發現更好。而且,編譯時版本對於常量初始值設定項是必需的。


這裏是我的兩個(失敗)的嘗試使在多個地方單機版的工作(作爲一個常量數組的大小,作爲一個枚舉初始化,並與變量的函數)。不幸的是,它們只能在編譯時(常量初始化)或運行時才工作 - 我想找出一個適用於這兩者的版本。

// CHECKEXPR_RETURNVAL - version "A" 
#define CTCHECK_EXPR(EXP)(CTCheckBool<EXP>::ExistsZeroIfTrue) 
template <bool bExpression> struct CTCheckBool {}; 
template <> struct CTCheckBool<true> {enum{ExistsZeroIfTrue=0};}; 
// Note: Plus ("+") is used rather than comma operator because 
// the comma operator can not be used for constant initializers 
#define CHECKEXPR_RETURNVAL_A(EXP,VAL) (CTCHECK_EXPR(EXP) + (VAL)) 

// Test Out version "A" -- works only for Compile Time Constants 
#define ALIGNPOW2_A(VAL,ALIGN) CHECKEXPR_RETURNVAL_A(\ 
    ISPOW2(ALIGN) , _ALIGNPOW2(VAL,ALIGN)) 

char AlignedVar_A[ALIGNPOW2_A(2,8)]; 
enum { AlignedVal_A = ALIGNPOW2_A(57,16) }; 

int TestAlignPow2_A(int val, int align) 
    {return(ALIGNPOW2_A(val,align));}  // Compile Error 


// CHECKEXPR_RETURNVAL - version "B" 
template<typename T> T CHECKEXPR_RETURNVAL_B(bool bExpr,T val) 
    { ASSERT(bExpr); return(val); } 

// Test Out version "B" -- works only for Runtime Computed Values 
#define ALIGNPOW2_B(VAL,ALIGN) CHECKEXPR_RETURNVAL_B(\ 
    ISPOW2(ALIGN) , _ALIGNPOW2(VAL,ALIGN)) 

char AlignedVar_B[ALIGNPOW2_B(2,8)];  // Compile Error 
enum { AlignedVal_B = ALIGNPOW2_B(57,16) }; // Compile Error 

int TestAlignPow2_B(int val, int align) 
    {return(ALIGNPOW2_B(val,align));} 

不幸的是,兩種版本都不適用於所有三種情況。是否有一個適用於所有情況的代碼結構?

+0

聽起來類似於http://stackoverflow.com/questions/3299834/c-compile-time-constant-檢測 – Shelwien 2010-08-03 22:19:58

+0

@Neil:這只是我在幾分鐘內寫出的一次性測試文件中試用的東西 - 它遠不及產品代碼。這些代碼當然是非法的,而且不能運行 - 這就是爲什麼我注意到它沒有編譯的問題。我要問在所有三種情況下如何正確做到這一點。但是,我不明白如何能夠驗證(特別是在編譯時)對宏的參數是否正確是如此可怕。也許你有什麼建設性的補充關於如何實現一個ALIGNPOW2(VAL,ALIGN)宏,以合法的方式檢查ALIGN是否爲2的冪? – Adisak 2010-08-03 22:30:35

+0

好的,您希望在返回VAL時測試EXPR是否爲「TRUE」的模板或宏(如果表達式爲「TRUE」,那麼正確?)。好的。現在,如果表達式是「FALSE」,那麼應該發生什麼?空陳述?編譯器錯誤? – SigTerm 2010-08-03 22:45:07

回答

0

好像你真的會使用的C++ 0x constexpr功能...

+0

不幸的是,我在一個不支持C++ 0x擴展的標準C++編譯器中這樣做。我希望用C語言來做。我的第一次嘗試並不適用於所有情況,這就是爲什麼我要問這裏的專家。 – Adisak 2010-08-03 22:34:15

0

嗯......不是一個完整的答案,但我認爲你可以得到這個,你想要什麼:

#include <stdio.h> 

template <int I> struct S{ 
    static void doIt(){ 
     fprintf(stderr, "wow\n"); 
    } 
}; 

template<> void S<0>::doIt(){ 
    fprintf(stderr, "oops\n"); 
} 

#define EXPR(p) S<(int)((bool)(p))>::doIt()  

int main(int argc, char** argv){ 
    EXPR((5+2)==7); 
    EXPR((5+2)==8); 
    const int a = 58; 
    EXPR(a==58); 
    EXPR(58); 
    return 0; 
} 

可以基於表達式得到一個編譯錯誤:

#include <stdio.h> 

template <int I> struct S{ 
    static void doIt(){ 
     ssdfkjehiu //deliberately invalid code 
     fprintf(stderr, "oops\n"); 
    } 
}; 

template<> void S<1>::doIt(){ 
    fprintf(stderr, "wow\n"); 
} 

#define EXPR(p) S<(int)((bool)(p))>::doIt() 



int main(int argc, char** argv){ 
    EXPR((5+2)==7); 
    EXPR((5+2)==8);//uncomment it to make code compile 
    const int a = 58; 
    EXPR(a==58); 
    EXPR(58); 
    return 0; 
} 

但是,這會導致錯誤將是一個漫長的模板錯誤信息的中間線。例如:

1.cpp(6) : error C2065: 'asdlfkjhasd' : undeclared identifier 
     1.cpp(4) : while compiling class template member function 'void S<I>::doIt(void)' 
     with 
     [ 
      I=0 
     ] 
     1.cpp(19) : see reference to class template instantiation 'S<I>' being compiled 
     with 
     [ 
      I=0 
     ] 
1.cpp(6) : error C2146: syntax error : missing ';' before identifier 'fprintf' 

正如您所看到的,錯誤是由消息中間提到的第19行引起的。這有點不方便。

我不能保證這兩個示例都不依賴於某些未定義的C++行爲。

另外,我認爲未來的代碼維護者可能不滿意這個...

附:我想你也應該看看提升。如果我沒有記錯的話,它有相當多的「魔術預處理器宏」(例如循環),所以它有可能實現了類似的東西。

- 編輯 -
好了,這個怎麼樣?:

#include <stdio.h> 
#include <string> 

template <typename T> void a(T &i){ 
    fprintf(stderr, "variable\n"); 
} 

void a(const char* const i){ 
    fprintf(stderr, "constant\n"); 
} 

void a(bool i){ 
    fprintf(stderr, "constant\n"); 
} 

int main(int argc, char** argv){ 
    int i; 
    float f; 
    std::string s; 
    const char* s1 = "b"; 
    a(3); 
    a(3+2); 
    a(1.0f); 
    a('a'); 
    a("b"); 
    a(i); 
    a(f); 
    a(s); 
    a(s1); 
    return 0; 
} 
+0

「故意無效的代碼」總是會對非VC++編譯器產生錯誤。如果我沒有弄錯,編譯器應該檢查非依賴的東西,即使模板沒有實例化,只是VC++省略了這一步。 - 如果要重新創建一個'static_assert',那麼就有符合標準的方法。 - 但是整個問題是,如何使代碼根據標識符是否爲編譯時常量來做不同的事情(這可能是不可能的,IMO)。 – UncleBens 2010-08-03 23:41:06

+0

@UncleBens:「但是整個問題是,如何讓代碼根據標識符是否是編譯時常量來做不同的事情(這可能是不可能的,IMO)」只需檢查(сompile/ run)第一個示例。不會產生錯誤的那個。至少在我的vs2008表達式中,它根據編譯時表達式做了不同的事情。 – SigTerm 2010-08-03 23:51:50

+0

我正在嘗試模板選擇的東西。我的問題是使用常量初始值設定項(如數組大小和枚舉賦值)在使用時需要模板常量成員變量,但我需要使用模板函數來處理變量輸入大小寫。我無法將它們與一件事綁定在一起,因爲您無法使用返回值調用函數,並且在模板中輕鬆地將變量初始化爲相同的東西。 – Adisak 2010-08-04 05:37:30

相關問題