2010-08-03 57 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

相关问题