简体   繁体   中英

How do you make C++ create an expression that uses compile-time checking for constants and asserts for variables?

Here's an example setup… a macro or a template CHECKEXPR_RETURNVAL(EXPR,VAL) that checks that EXPR is TRUE while returning VAL.

This is useful in a variety of places -- like in this highly simplified example:

#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) )

So, the difficulty is this: I want to do compile time checks if possible, and if the value is not a constant that is determinate at compile time, then do a runtime check.

Basically, the idea is to catch bad parameters as soon as possible; if you can catch a bad parameter at compile time that's better than finding out at run time. Also, the compile time version is required for constant initializers.


Here are my two (failed) attempts to make single version work in multiple places (as a constant array size, as an enum initializer, and in a function with variables). Unfortunately, they either work for the compile time only (constant initializer) or the runtime only -- I would like to figure out a version that will work for both.

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

Unfortunately, neither version works for all three cases. Is there a code structure that will work for all the cases ?

似乎您真的会使用c ++ 0x constexpr函数...

Well... Not a complete answer, but I think you can derive what you want from this:

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

You can get a compiler error based on expression:

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

But the line that causes error will be in a middle of a lengthy template error message. Example:

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'

As you see, error is caused by line 19, which is mentioned in the middle of the message. This is a bit inconvenient.

I cannot guarantee that both examples doesn't rely on some undefined C++ behavior.

Also, I think that the next code maintainer may not be happy about this...

PS I think you should also take a look at boost. If I remember correctly, it had quite a lot of "magic preprocessor macro" (loops, for example), so it is possible that it implemented something similar.

--edit--
Okay, what about this one?:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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