6

我目前有一个模板函数,根据它的模板参数 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)没有这种虚假警告,因为移位距离是那里的运行时变量。

4

12 回答 12

6

使用 C++11 或提升。

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>();
}
于 2013-01-31T14:17:56.800 回答
4

将 (AB) 和 (BA) 的结果转换为无符号,并另外用(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)不会出现在实际代码中,但“受保护的取消引用”通常是一种常见的习惯用法。如果检查的指针实际为空,它本身当然不会产生未定义的行为;这就是警卫有用的原因。

于 2013-01-31T14:17:48.213 回答
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) >() );
}

(使用这样的判别类模板是更简单的元编程技术之一。)

于 2013-01-31T14:20:38.317 回答
1

davmac 的评论(“使用 &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));
}
于 2013-01-31T14:45:13.860 回答
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您可以为(<<=>>=)做类似的事情。

于 2013-01-31T14:25:00.210 回答
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";
}
于 2013-01-31T15:54:06.987 回答
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)));
}

我相信一种更简洁的方法可能是发送到一个知道如何转移正确方向的真/假专用模板。

于 2013-01-31T15:39:53.783 回答
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) ) );
}
于 2013-01-31T15:13:42.393 回答
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). 那应该行得通。

于 2013-01-31T14:19:04.840 回答
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();
}
于 2013-01-31T14:19:53.117 回答
0

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

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();
}
于 2013-01-31T14:20:54.000 回答
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));
}

偏移为负的分支将被丢弃

于 2018-10-02T09:21:24.147 回答