How could my code tell a compile-time constant versus a variable?
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 ov开发者_高级运维erhead 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?
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.
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.
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.
精彩评论