views:

108

answers:

2

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 ?

A: 

It seems like you would really make use of c++0x constexpr functions...

jpalecek
Unfortunately, I'm doing this in a standard C++ compiler that does not support C++0x extensions. I'm hoping to do it using the C language. My first attempts did not work for all cases which is why I'm asking the experts here.
Adisak
A: 

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...

P.S. 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;
}
SigTerm
The "deliberately invalid code" would produce an error always with a non-VC++ compiler. If I'm not mistaken, compilers are supposed to check non-dependent stuff even if the template is not instantiated, except that VC++ omits this step. - If the point is to reinvent a `static_assert`, then there are standard-compliant ways. - But the entire question is, how to make code do different things depending on whether an identifier is a compile-time constant or not (which might be impossible, IMO).
UncleBens
@UncleBens: "But the entire question is, how to make code do different things depending on whether an identifier is a compile-time constant or not (which might be impossible, IMO)" Just check(сompile/run) first example. The one that doesn't produce errors. At least on my vs2008 express it does different things based on compile-time expression.
SigTerm
I was trying stuff with Template selection. My problem was to use as a constant initializers (like array size and enum assignment) require a template constant member variable while using but I needed to use a template function to handle the variable input cases. I couldn't tie them together with one thing because you can't make calling a function with a return value and initializing a variable look like the same thing easily in a template.
Adisak
The first example only works with *compile-time* constants. OP is interested in doing something different with values, that *cannot be used for non-type template arguments*.
UncleBens
@UncleBens: I still don't understand what exactly is the problem: http://www.daniweb.com/code/snippet271450.html . If OP wants to use templates for value initialization, there are enums and static variables for that. http://en.wikipedia.org/wiki/Template_(programming) Downvoting question as unclear.
SigTerm
IMO, the title says it all. OP wants a FOOBAR, so that `FOOBAR(x)` uses static assertion if x is a compile-time constant and runtime assert if x is not a compile-time constant. `int main() { const int a = 10; FOOBAR(a); const int b = foo(); FOOBAR(b); }` In this snippet `a` is a compile-time constant, so FOOBAR should do compile-time checks. `b` is a run-time value, so FOOBAR should produce a run-time `assert`.
UncleBens
@UncleBens: The first statement (*"a macro or a template CHECKEXPR_RETURNVAL(EXPR,VAL) that checks that EXPR is TRUE while returning VAL."*) doesn't exactly match question's title. It gets more interesting later: *"results of ALIGNPOW2 (and hence CHECKEXPR_RETURNVAL) can be used for initialization of const’s or enum’s or as expressions defining static array size, etc. However, it’s possible that variables could be passed to the same ALIGNPOW2 macro in runtime."*
SigTerm
Yes, the question itself is a bit unclear because it is dominated by failed attempts to solve it. But the intention seems to be: how to have things checked at compile-time if possible, else fall back to runtime checks. To me the central question seems to be: how to determine if a value is a compile-time constant without an error if it isn't. As far as I can see, template metaprogramming (alone) can't deal with it (since it requires compile-time constants in the first place).
UncleBens
@UncleBens: "To me the central question seems to be: how to determine if a value is a compile-time constant without an error if it isn't." So, you think that in short the problem is "how call one routine if a value is constant expression, and another if it is variable"? Hmm...
SigTerm
Uncle Bens has it exactly right. I want to do compile time checks if possible, and if the value is not constant that is determinate at compile time, then do a runtime check.
Adisak
@Adisak: Added another example. Still not a complete answer, but different behavior based on whether variable is constant or not. Not thoroughly tasted, though.
SigTerm