I currently have a template function which, depending on its template parameters A and B, may shift a value either left or right:
template <int A, int B> void f(X) {
// ...
if (A >= B)
{
SetValue(X << (A-B));
}
else // (A < B)
{
SetValue(X >> (B-A));
}
When I instantiate the template for A<B
, I get a warning for a negative shift right on the (unreachable) first branch, and else I get a warning for a negative shift left on the first branch. Our codebase is warning-free so this isn't acceptable. Is there a concise, readable alternative to these two shift statements?
Similar questions (e.g. Dynamically shift left OR right) don't have this spurious warning as the shift distance is a runtime variable there.
With C++11 or boost.
template<int A, int B>
void f_impl(typename std::enable_if<(A >= B)>::type* = 0)
{
// first case
}
template<int A, int B>
void f_impl(typename std::enable_if<(A < B)>::type* = 0)
{
// second case
}
template<int A, int B>
void f()
{
f_impl<A, B>();
}
Cast the result of (A-B) and (B-A) to unsigned, and additionally mask (bitwise-and) it with (sizeof(int) - 1)
. This clears the warning for GCC 5.5 and 6.3. For more recent versions of GCC no warning is generated.
template <int A, int B> void f(int X) {
// ...
if (A >= B)
{
SetValue(X << ((unsigned)(A-B) & (sizeof(int) - 1)));
}
else // (A < B)
{
SetValue(X >> ((unsigned)(B-A) & (sizeof(int) - 1)));
}
}
Note
to address the various comments about undefined behaviour: the only sense in which this proposed solution might cause undefined behaviour is by performing a shift of an amount greater than the bit-width of the operand. However, this is guarded by the comparison; assuming that the difference between A and B is a safe shift count, which is implied in the question, then if (A >= B)
ensures that that only a shift with that amount actually executes. The other branch of the if
statement is not executed and so does not perform a shift and cannot produce undefined behaviour from the shift (although if it were executed, it certainly would do so).
A couple of commenters have made an assertion that the branch which is not executed can still cause undefined behaviour. I am somewhat at a loss as to how such a miscomprehension could occur. Consider the following code:
int *a = nullptr;
if (a != nullptr) {
*a = 4;
}
Now, if dereference of a null pointer causes undefined behaviour even when it is not executed, the guard condition becomes useless. This is clearly not the case. The above code is perfectly fine; it assigns a
a value of nullptr
, and doesn't then dereference a
, due to the guard. Although such obvious examples (with the assignment to null immediately followed by a check for null) do not tend to occur in real code, the "guarded dereference" in general is a common idiom. It certainly does not by itself produce undefined behaviour if the pointer checked actually is null; that's why the guard is useful.
The most obvious is to forward to a function taking an additional argument:
template <bool Cond> struct Discrim {};
template <int A, int B>
void f( Discrim<false> )
{
SetValue( X, (A - B) );
}
template <int A, int B>
void f( Discrim<true> )
{
SetValue( X, (B - A) );
}
template <int A, int B>
void f()
{
f( Discrim< (A < B) >() );
}
(Use of such a Discrim class template is one of the simpler meta-programming techniques.)
davmac's comment ("use &0x1F") was the right idea, except for the assumed maximum shift width. That was easily fixed:
template <int A, int B> void f(X) {
// ...
if (A >= B)
{
SetValue(X << abs(A-B));
}
else // (A < B)
{
SetValue(X >> abs(B-A));
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With