5

Here's my problem. I have a BINARY_FLAG macro:

#define BINARY_FLAG( n ) ( static_cast<DWORD>( 1 << ( n ) ) )

Which can be used either like this ("constant" scenario):

static const SomeConstant = BINARY_FLAG( 5 );

or like this ("variable" scenario):

for( int i = 0; i < 10; i++ ) {
    DWORD flag = BINARY_FLAG( i );
    // do something with the value
}

This macro is not foolproof at all - one can pass -1 or 34 there and there will at most be a warning yet behavior will be undefined. I'd like to make it more foolproof.

For the constant scenario I could use a template:

template<int Shift> class BinaryFlag {
staticAssert( 0 <= Shift && Shift < sizeof( DWORD) * CHAR_BIT );
public:
static const DWORD FlagValue = static_cast<DWORD>( 1 << Shift );
};
#define BINARY_FLAG( n ) CBinaryFlag<n>::FlagValue

but this will not go for the "variable" scenario - I'd need a runtime assertion there:

inline DWORD ProduceBinaryFlag( int shift )
{
    assert( 0 <= shift && shift < sizeof( DWORD) * CHAR_BIT );
    return static_cast<DWORD>( 1 << shift );
}
#define BINARY_FLAG( n ) ProduceBinaryFlag(n)

The latter is good, but has no compile-time checks. Of course, I'd like a compile-time check where possible and a runtime check otherwise. At all times I want as little runtime overhead as possible so I don't want a function call (that maybe won't be inlined) when a compile-time check is possible.

I saw this question, but it doesn't look like it is about the same problem.

Is there some construct that would allow to alternate between the two depending on whether the expression passed as a flag number is a compile-time constant or a variable?

Community
  • 1
  • 1
sharptooth
  • 167,383
  • 100
  • 513
  • 979
  • `assert( 0 <= flag && flag < 32 )`... did you mean `flag` ==means==> `shift` here ? Also, if you pass an arguement to `ProduceBinaryFlag()` at runtime, then how can you check at compile time ? Or am I missing anything ? – iammilind Aug 04 '11 at 09:58
  • Another example: `const int myflag = 5; BINARY_FLAG(myflag);`. `myflag` is a compile-time constant, but not a literal. The example in the question only uses one special case of a compile-time constant. – Steve Jessop Aug 04 '11 at 09:59
  • A thought - if you're willing to assume a C++ implementation that supports VLAs, then the macro could first do a runtime check, then declare an array whose size is positive if `n` is in range and negative otherwise. In the case where `n` is an integer constant expression, this will fail at compile-time provided that the expression denoting the size is also an ICE. In the case where `n` is not constant, UB will be prevented by the runtime check. Since the array is small and unused, hopefully it won't have significant cost. – Steve Jessop Aug 04 '11 at 10:06
  • @iammilind: I fixed the code. Well, yes, if I pass it into a function it's not a compile time check anymore and that is what I want to avoid. – sharptooth Aug 04 '11 at 10:11
  • Did you try actually looking at the generated assembly? I would expect the function to not only be inlined 100% of the time, but to be subsequently optimized into the result of the calculation where possible (i.e. because the parameter is known at runtime). At worst there would be a spurious call to `assert(false)`. But what's wrong with just expecting the caller to use the template version with compile-time constants and the parameterized version with runtime values? – Karl Knechtel Aug 04 '11 at 10:19
  • 3
    In the current state of C++0x implementations, this is not cleanly possible. When compilers implement `constexpr`, you'll have a way. In the meantime, I don't see why your macro isn't OK. – Alexandre C. Aug 04 '11 at 10:20
  • @Alexandre C.: Which one macro implementation do you mean? – sharptooth Aug 04 '11 at 10:22
  • @sharptooth: the first one. Selecting an expression based on its `constexpr`ness doesn't seem possible to me given current implementations of C++0x standard (and completely impossible with C++03 compilers). – Alexandre C. Aug 04 '11 at 10:23
  • I can try one construct for `BINARY_FLAG(n)` where `n` can only be numbers. If it's some named variable or constant then it will throw error. But that's what you want ? I mean do you want that `BINARY_FLAG()` should be passed only number literals and nothing else. – iammilind Aug 04 '11 at 10:24
  • @iammilind: No, I want it to be passed anything and behave differently depending on whether it is a compile-time known constant of any kind or a variable. – sharptooth Aug 04 '11 at 10:38

3 Answers3

2

This is simpler than you think :)

Let's have a look:

#include <cassert>

static inline int FLAG(int n) {
    assert(n>=0 && n<32);
    return 1<<n;
}

int test1(int n) {
    return FLAG(n);
}
int test2() {
    return FLAG(5);
}

I don't use MSVC, but I compiled with Mingw GCC 4.5:

g++ -c -S -O3 08042.cpp

The resulting code for first method looks like:

__Z5test1i:
    pushl   %ebp
    movl    %esp, %ebp
    subl    $24, %esp
    movl    8(%ebp), %ecx
    cmpl    $31, %ecx
    ja  L4
    movl    $1, %eax
    sall    %cl, %eax
    leave
    ret
L4:
    movl    $4, 8(%esp)
    movl    $LC0, 4(%esp)
    movl    $LC1, (%esp)
    call    __assert
    .p2align 2,,3

And the second:

__Z5test2v:
    pushl   %ebp
    movl    %esp, %ebp
    movl    $32, %eax
    leave
    ret

See? The compiler is smart enough to do it for you. No need for macros, no need for metaprogramming, no need for C++0x. As simple as that.

Check if MSVC does the same... But look - it's really easy for the compiler to evaluate a constant expression and drop the unused conditional branch. Check it if you want to be sure... But generally - trust your tools.

Kos
  • 70,399
  • 25
  • 169
  • 233
  • This is just great except I won't get a compile-time error for surely illegal usecases. Runtime error will only occur on some dataset and a compile-time error would occur immediately upon compilation. – sharptooth Aug 04 '11 at 13:05
  • True. How is that static assert implemented? With the above, all we need is a static assertion which would compile silently into nothing if its parameter cannot be evaluated at compile time. – Kos Aug 04 '11 at 13:21
  • Which *might* be impossible since a template parameter can be either a typename or a constant expression... Hm. This needs Trickery! – Kos Aug 04 '11 at 13:27
1

I suggest you use two macros. BINARY_FLAG CONST_BINARY_FLAG That will make your code easier to grasp for others. You do know, at the time of writing, if it is a const or not. And I would in no case worry about runtime overhead. Your optimizer, at least in VS, will sort that out for you.

Adam
  • 637
  • 1
  • 8
  • 21
1

It's not possible to pass an argument to a macro or function and determine if it's compile time constant or a variable.

The best way is that you #define BINARY_FLAG(n) with compile time code and place that macro everywhere and then compile it. You will receive compiler-errors at the places where n is going to be runtime. Now, you can replace those macros with your runtime macro BINARY_FLAG_RUNTIME(n). This is the only feasible way.

iammilind
  • 68,093
  • 33
  • 169
  • 336