Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Elegant way to express shift left OR right in template

Tags:

c++

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.

like image 625
MSalters Avatar asked Jan 31 '13 14:01

MSalters


4 Answers

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>();
}
like image 147
ForEveR Avatar answered Oct 19 '22 14:10

ForEveR


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.

like image 43
davmac Avatar answered Oct 19 '22 12:10

davmac


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.)

like image 3
James Kanze Avatar answered Oct 19 '22 14:10

James Kanze


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));
}
like image 1
MSalters Avatar answered Oct 19 '22 12:10

MSalters