Compile-time checking if right shift is arithmetic on signed types
I am wondering what is the most portable way of checking whether right shift is arithmetic when operating for on signed types (e.g. whether -2 >> 1
is -1
) at compile-time.
My idea is to check this somehow on compile time and be able to detect this, so I can compile different versions of the function (depending whether the operator >>
is really arithmetic shift or not).
By reading the topic Verifying that C / C++ signed right shift is arithmetic for a particular compiler? I came to the idea to initialize a flag
static const bool is_arithmetic_rs = (((signed int)-1)>>1) == ((signed int)-1));
and to test it at run-time like this:
if (is_arithmetic_rs) {
// some fast algorithm using arithmetic right shifts (using >> operator)
} else {
// the same algorithm without arithmetic right shifts (much slower)
}
However, I would like to avoid this branching if possible every time. For simplicity, let's suppose I want to implement a portable arithmetic right shift; if I had to check this every time a function is called, this w开发者_运维问答ould be a huge performance impact, so I would like to do it at compile time, if possible.
If there exist no portable way of doing this check, is there a way to do this by checking on the best-effort basis, like checking with ifdefs for a specific compiler/platform?
The best way to perform such checks is what e.g. GNU autotools do:
Compile a small program on your target platform and test what happens
Set an appropriate
#define
in a header fileInclude that header file in your source files
Optionally, use appropriately defined macros so that you don't clutter your code with
#ifdef
directives for every little thing.Compile your main project
That way you avoid having to create tables with the supported features and the various quirks of each hardware platform and operating system in the wild - let alone their combinations. If you do not build your code on your target, however, you will have to replace the first step with a pre-supplied table/list of features for your target.
You should probably have a look at widely used build systems such as GNU autotools or CMake, in order to reuse existing macros and platform-specific information and avoid having to create your own and therefore reinvent the wheel.
BTW, any decent compiler these days should optimise-out simple tests with constant expressions, so using a runtime test where necessary - perhaps through a macro - should not hurt performance too much. You should test and profile your code to find out.
The branch could be avoided by using a preprocessing-time test
#if ((-1)>>1) == (-1))
...
#else
...
#endif
Really more of a comment than answer (but apparently I'm not reputable)
A couple of the answers here use pre-processor checks like
#if ((-1)>>1) == (-1))
Personally, I wouldn't trust the pre-processor to tell me what kind of code the compiler generates.
Have you actually verified that your compiler doesn't optimize division into arithmetic shift when it's available?
Otherwise I think you can use templates.
template <bool B>
void do_work();
template <>
void do_work<true>()
{
// Do stuff with H/W.
}
template <>
void do_work<false>()
{
// Do slow stuff with S/W.
}
do_work<(-2 >> 1) == -1>();
Then make it prettier to use with an inline function:
inline real_do_work()
{
do_work<(-2 >> 1) == -1>();
}
Try this:
#define SAR(x,y) ((x)>=0) ? ((x)>>(y)) : (~(~(x)>>(y)))
A good compiler will optimize this to ((x)>>(y))
assuming the CPU is sane.
Feedback is welcome as to which compilers are good.
Inspired Giuseppe's and R..'s answers:
#if -2 >> 1 == -1
#define rshift_ar(X, Y) ((X) >> (Y))
#elif ~(~(-2) >> 1) == -1
#define rshift_ar(X, Y) ((X) >= 0 ? (X) >> (Y) : ~(~(X) >> (Y)))
#else
#error "unsupported shifting semantics"
#endif
All this preprocessor magic is useless with any decent compiler. Did you verify that your code given above really generates a branch in its output? I highly doubt it, since the static const bool
can an will be evaluated as a compile time constant, so the false
-part of your if (is_arithmetic_rs)
will be eliminated by the compiler, with anything above -O1
. See also my answer here.
Besides, I doubt that the preprocessor's output is guaranteed to be the same as the compiler's, especially when cross-compiling between platforms that have different shifts.
精彩评论