在模板中表达左移或右移的优雅方式

时间:2013-01-31 14:14:08

标签: c++

我目前有一个模板功能,根据其模板参数A和B,可以向左或向右移动一个值:

template <int A, int B> void f(X) {
// ...
if (A >= B)
{
  SetValue(X << (A-B));
}
else // (A < B)
{
  SetValue(X >> (B-A));
}

当我为A<B实例化模板时,我在(无法到达)第一个分支上收到负向移位警告,否则我会收到第一个分支上留下负移位的警告。我们的代码库没有警告,所以这是不可接受的。这两个转换语句是否有简洁易读的替代方法?

类似的问题(例如Dynamically shift left OR right)没有这个虚假警告,因为移位距离是那里的运行时变量。

12 个答案:

答案 0 :(得分:5)

使用C ++ 11或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>();
}

答案 1 :(得分:4)

将(A-B)和(B-A)的结果转换为无符号,并另外使用(sizeof(int) - 1)对其进行掩码(按位)。这清除了GCC 5.5和6.3的警告。对于更新版本的GCC,不会生成任何警告。

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)));
  }
}

注意 解决有关未定义行为的各种注释:此提议的解决方案可能导致未定义行为的唯一意义是通过执行大于操作数的位宽的量的移位。然而,比较保护了这一点;假设A和B之间的差异是问题中隐含的安全移位计数,那么if (A >= B)确保只有具有该数量的移位实际执行。 if语句的另一个分支是执行,因此不执行移位并且不能从移位产生未定义的行为(尽管如果它被执行,它肯定会这样做)。

一些评论者断言未执行的分支仍然可能导致未定义的行为。关于如何发生这种误解,我有点不知所措。请考虑以下代码:

int *a = nullptr;

if (a != nullptr) {
    *a = 4;
}

现在,如果取消引用空指针会导致未定义的行为,即使它未被执行,保护条件也会变得无用。事实显然并非如此。上面的代码非常好;它会为a分配nullptr的值,但由于后卫而不会取消引用a。虽然这些明显的例子(赋值为null后立即检查为null)并不倾向于在实际代码中出现,但“保护的取消引用”通常是一种常见的习惯用法。如果实际检查的指针为null,它当然不会产生未定义的行为;这就是守卫有用的原因。

答案 2 :(得分:3)

最明显的是转发到一个功能 附加论点:

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) >() );
}

(使用这样的Discrim类模板是最简单的一种 元编程技术。)

答案 3 :(得分:1)

这是我在草稿引擎中使用的,它大量使用位板作为其电路板表示

namespace detail {

enum { Left, Right };

template<typename, std::size_t>
struct Shift;

// partial specialization for bitwise shift-left
template<std::size_t N>
struct Shift<Left, N>
{
        template<typename T>
        T operator()(T x) const
        {
                return x << N;
        }
};

// partial specialization for bitwise shift-right
template<std::size_t N>
struct Shift<Right, N>
{
        template<typename T>
        T operator()(T x) const
        {
                return x >> N;
        }
};

} // namespace detail

template<int N>
struct Shift
{
        template<typename T>
        T operator()(T x)
        {            
            return N >= 0 ? detail::Shift<Left, N>()(x) : detail::Shift<Right, -N>()(x);
        }
};

template <int A, int B> 
void f(int x)
{
     SetValue(Shift<A-B>()(x));
}

您可以对ShiftAssign<<=>>=)执行类似操作。

答案 4 :(得分:1)

除了假定的最大移位宽度之外,davmac的注释(“use&amp; 0x1F”)是正确的想法。这很容易解决:

template <int A, int B> void f(X) {
// ...
if (A >= B)
{
  SetValue(X << abs(A-B));
}
else // (A < B)
{
  SetValue(X >> abs(B-A));
}

答案 5 :(得分:0)

您可以添加新模板,并对其进行适当的专门化,例如:

template<bool b> int Shift(int i, int a, int b);

template<true> int Shift(int i, int a, int b) { return i << (a-b); }
template<false> int Shift(int i, int a, int b) { return i >> (b-a); }

然后将其作为Shift<(A >= B)>(X, A, B)调用。这应该有用。

答案 6 :(得分:0)

脱离我的头顶:

template <int A, int B> struct whatever {
    static void f() {
        SetValue(X << (A - B));
    }
};

template <int A, int B, bool reversed> helper : whatever<A, B> {
};

template <int A, int B, true> : helper whatever<B, A> {
};

template <int A, int B> do_it : helper<A, B, B < A> {
};

template <int A, int B> void f() {
    return do_it<A, B>::f();
}

答案 7 :(得分:0)

您可以将移位操作放在单独的结构中,并在C ++ 11中使用std::conditional

template <typename A, typename B, typename X>
struct ShiftRight
{
    static void shift() { SetValue(X >> (A - B)); }
};

template <typename A, typename B, typename X>
struct ShiftLeft
{
    static void shift() { SetValue(X << (A - B)); }
};

template <typename A, typename B, typename X>
void f()
{
    typedef typename std::conditional<A >= B, ShiftLeft<A, B, X>, ShiftRight<A, B, X>>::type ShiftType;

    ShiftType::shift();
}

答案 8 :(得分:0)

template< int A, int B > void f(X)
{
    std::function< int(int, int) > shift =
        A < B
        ? [](int X, int N) { return X << N; }
        : [](int X, int N) { return X >> N; }

    SetValue( shift( X, std::max(A,B) - std::min(A,B) ) );
}

答案 9 :(得分:0)

我认为一个相当小的改变就是用一个乘法将未执行的移位归零。编译器仍然可以在编译时完成所有工作:

template <int A, int B> void f(X) {
// ...
if (A >= B)
{
  SetValue(X << ((A < B) * (A-B)));
}
else // (A < B)
{
  SetValue(X >> ((A >= B) * (B-A)));
}

我认为更清晰的方法可能是派遣到一个知道如何改变正确方向的真/假专用模板。

答案 10 :(得分:0)

这样的事情: -

#include <iostream>

template <int A, int B, bool D> class shift
{
};

template <int A, int B> class shift<A, B, false>
{
public:
    static int result(int x) {return x << (B-A);}
};

template <int A, int B> class shift<A, B, true>
{
public:
    static int result(int x) {return x >> (A-B);}
};


template <int A, int B> int f(int x)
{
    return shift<A, B, (A>B)>::result(x);
}

int main()
{
    std::cout << f<1, 2>(10) << "\n";
    std::cout << f<2, 1>(10) << "\n";
}

答案 11 :(得分:0)

要回答我自己的问题:使用C ++ 17,现在很容易。

template <int A, int B> void f(X) {
if constexpr (A >= B)
// ^^^^^^^^^
{
  SetValue(X << (A-B));
}
else // (A < B)
{
  SetValue(X >> (B-A));
}

偏移为负的分支将被丢弃