1

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 ?

Adisak
  • 6,708
  • 38
  • 46
  • Sounds similar to http://stackoverflow.com/questions/3299834/c-compile-time-constant-detection – Shelwien Aug 03 '10 at 22:19
  • @Neil: This is just something I'm trying out in a throwaway test file that I wrote in a couple minutes - it's nowhere near production code. The code is of course illegal and non-functioning - that's why I note the problems with it not compiling. I'm asking how to do it correctly in all three cases. But, I don't see how it's so horrible to be able to want to validate (especially at compile-time) that parameters to macros are correct. Perhaps you have something constructive to add about how to implement a ALIGNPOW2(VAL,ALIGN) macro that checks that ALIGN is Power-of-2 in a legal way? – Adisak Aug 03 '10 at 22:30
  • Okay, you want template or macros that tests if EXPR is "TRUE" while returning VAL (if expression is "TRUE", correct?). Okay. Now, if expression is "FALSE", what is supposed to happen? Empty statement? Compiler error? – SigTerm Aug 03 '10 at 22:45
  • @Shelwien: trying to use that method with ?: operator to select based on the is_const() only yields a version which no longer works for any of the three use cases I suggested :-( – Adisak Aug 03 '10 at 23:10
  • @SigTerm: If it's a constant that can be determined at compile time and the value is FALSE, then Compiler Error... basically like the many Compile-Time / Static Asserts already out there. Otherwise for variables, a run-time assert (that could compile out of release builds) would be used. – Adisak Aug 03 '10 at 23:18
  • -1. Your question is vague/unclear. You need to provide more examples on intended usage of that macro. On other hand, see http://en.wikibooks.org/wiki/C%2B%2B_Programming/Templates/Template_Meta-Programming http://www.daniweb.com/code/snippet271450.html – SigTerm Aug 04 '10 at 11:53
  • @SigTerm: The three test cases I point out show how I want to use it... I'm pretty explicit that I want to be able to use it for both places where you require a constant initializer (i.e. array sizes and and enums) as well as where variables might be involved. I'm beginning to guess this simply can not be done in C++. – Adisak Aug 04 '10 at 18:17
  • @SigTerm: I made some clarifications based on feedback from UncleBens but his understanding of my question is 100% correct. – Adisak Aug 04 '10 at 18:31
  • @Adisak: So, compile-time check for constants, and run-time check for variables in a single operator/macro/whatever. For compile-time checks - compiler error if it fails, and for run-time checks - crash application if it fails. Correct? – SigTerm Aug 04 '10 at 18:36
  • @Sigterm: Yeah, that is exactly right. Basically, catch the bad parameter as soon as possible - if you can catch a bad parameter at compile-time that's better than run-time! Plus it's required to do it at compile-time for anything requiring a constant initializer. – Adisak Aug 04 '10 at 19:16

2 Answers2

0

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

jpalecek
  • 47,058
  • 7
  • 102
  • 144
  • 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 Aug 03 '10 at 22:34
0

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
  • 26,089
  • 6
  • 66
  • 115
  • 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 Aug 03 '10 at 23:41
  • @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 Aug 03 '10 at 23:51
  • 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 Aug 04 '10 at 05:37
  • 1
    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 Aug 04 '10 at 06:37
  • @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 Aug 04 '10 at 12:02
  • 1
    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 Aug 04 '10 at 16:02
  • @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 Aug 04 '10 at 16:58
  • 1
    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 Aug 04 '10 at 17:11
  • @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 Aug 04 '10 at 17:16
  • 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 Aug 04 '10 at 18:24
  • @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 Aug 04 '10 at 19:02