【问题标题】:Overloading bitshift operator in c++在 C++ 中重载位移运算符
【发布时间】:2017-07-12 03:55:43
【问题描述】:

我想重载在arm_neon.h 中定义在ARM 系统上的uint32x4_t 的位移运算符。

struct uint32x4_t {
   uint32_t val[4];
};

这应该通过调用 SIMD 函数来完成,该函数期望值发生变化并保持立即数:

uint32x4_t simdShift(uint32x4_t, constant_immediate);

shift.h

#ifndef SHIFT_H
#define SHIFT_H

namespace A {
   namespace B {
      /*uint32x4_t simdLoad(uint32_t*) {
         ...
      }*/

      template<int N>
      uint32x4_t shiftRight(uint32x4_t vec) {
        return vshrq_n_u32(vec,N);
      }
   }
}
uint32x4_t operator>>(uint32x4_t const & vec, const int v) {
  return A::B::shiftRight<v>(vec);
}
#endif

main.cpp

#include "shift.h"

int main() {
   uint32_t* data = new uint32_t[4];
   data[0] = 1;
   data[1] = 2;
   data[2] = 3;
   data[3] = 4;
   uint32x4_t reg;// = simdLoad(data);
   reg = reg>>3;
   return 0;
}

此代码产生错误:

‘uint32x4_t operator>>(const uint32x4_t&, int)’必须有参数 类或枚举类型的 uint32x4_t 运算符>>(uint32x4_t const & vec, const int v) {

对于像uint32x4_t 这样的“本机”类型,是否有解决方法来重载operator&gt;&gt;

编辑:我调整了建议的解决方法,但错误仍然保持不变:(

【问题讨论】:

  • 你需要把算子纳入范围; using namespace A::B; 是一个简单的修复方法。顺便说一句,simdShift 期望的 constant_immediate 必须是一个常量表达式const int v 参数不够用。
  • @ildjarn 但我不能将 constexpr 传递给函数,可以吗?
  • @Hymir :不像int,不。但是您可以将其设为函数模板并传递std::integral_constant&lt;int&gt; 的实例,这将起作用。您可以使用变量模板或 UDL 使构造integral_constant 实例清晰易读。
  • 这是处理 constant_immediate 的绝佳提示!非常感谢!

标签: c++ arm operator-overloading c++14 simd


【解决方案1】:

对 ErmIg 答案的逐步改进:

template<int N>
constexpr std::integral_constant<int, N> i_{};

template<int N>
uint32x4_t operator >>(uint32x4_t value, std::integral_constant<int, N>) noexcept {
    return _mm_slli_si128(value, N);
}

int main() {
    std::uint32_t data[4] = {1, 2, 3, 4};
    uint32x4_t reg;// = simdLoad(&data);
    reg = reg >> i_<3>;
}

注意我已将operator&gt;&gt; 放在全局命名空间中;如果你想把它放在不同的命名空间中,你需要在使用它之前将操作符带入作用域。

【讨论】:

    【解决方案2】:

    “uint32x4_t 是原生类型,由 arm_neon.h 提供。” (来自另一条评论)。

    您最初面临的问题是 C++ 使用了一种称为 Argument-Dependent Lookup 的东西。对于A::B::uint32x4,C++ 会考虑A::B::operator&gt;&gt;(uint32x4, int)。也就是说,C++ 会在各个参数的命名空间中查找。

    您的问题是 uint32x4 在全局命名空间中,而您将 operator&gt;&gt; 放在另一个命名空间中。那是错误的。将其放在正确的命名空间中。

    请注意,命名空间是为避免名称冲突而提供的两种机制之一。重载是另一种机制。命名空间适用于所有类型的名称:变量、类型和函数。重载仅适用于函数。但在这种情况下,这就足够了,因为运算符是函数的子集。您不会遇到名称冲突;您的 operator&gt;&gt; 与其他 operator&gt;&gt; 重载。

    【讨论】:

      【解决方案3】:

      为了调用具有常量立即数的函数(它经常在 SIMD 内在函数中遇到),我通常使用带有整数模板参数的模板函数。以下示例使用的是 SSE2,但对于 NEON 则类似:

      template<int shift> __m128i Shift(__m128i value)
      {
          return _mm_slli_si128(value, shift);
      }
      
      int main()
      {
          __m128i a = _mm_set1_epi8(3);
          __m128i b = Shift<2>(a);
          return 0;
      }
      

      不幸的是,我不知道如何为 C++ 运算符制作它。当然,我们可以创建一个带有模板参数的运算符,但是使用起来很不方便:

      template<int shift> __m128i operator >> (__m128i value, int shift_)
      {
          return _mm_slli_si128(value, shift);
      }
      
      int main()
      {
          __m128i a = _mm_set1_epi8(3);
          __m128i b = operator >> <2>(a, 2);
          return 0;
      }
      

      受@ildjarn 启发的变体:

      template<int N> struct Imm {};
      
      #define IMM(N) Imm<N>()
      
      template<int shift> __m128i operator >> (__m128i value, Imm<shift>)
      {
          return _mm_slli_si128(value, shift);
      }
      
      int main()
      {
          __m128i a = _mm_set1_epi8(3);
          __m128i b = a >> IMM(2);
          return 0;
      }
      

      【讨论】:

      • 这正是我实现它的方式!但我认为 operator>> 会更好阅读。
      【解决方案4】:

      一种解决方案是将operator&gt;&gt;A::B 命名空间移动到全局命名空间。如果所有其他符号都是不同的名称空间,那么您只需要限定它们。例如,如果simdShiftA::B 中,您仍然可以像这样拥有全局operator&gt;&gt;

      uint32x4_t operator>>(uint32x4_t const & vec, const int v) {
          return A::B::simdShift(vec, v);
      }
      

      但我认为让operator&gt;&gt; 成为uint32x4_t 的成员更合适:

      struct uint32x4_t {
          uint32_t val[4];
          uint32x4_t operator>>(const int v) const;
      };
      
      namespace A { namespace B {
      /// TODO: Put declaration/definition of simdShift here
      }} // namespace A { namespace B {
      
      uint32x4_t uint32x4_t::operator>>(const int v) const {
          return A::B::simdShift(*this, v);
      }
      

      或者,正如ildjarn 在评论中建议的那样,将符号从A::B 命名空间拉到您使用它们的上下文中:

      using namespace A::B;
      

      【讨论】:

      • 这看起来很棒,但 uint32x4_t 是一种原生类型,由 arm_neon.h 提供。因此,我不能简单地将 operator>> 添加为成员,可以吗?
      • @Hymir 不是会员,不是。但是您可以为现有类型全局定义运算符。如果您希望它成为成员,则必须先将 uint32x4_t 包装为您自己的类型,然后将运算符添加到该类型中。
      • @RemyLebeau 如果我这样使用它: namespace A{ namspace B { template uint32x4_t shiftRight(uint32x4_t vec) { return vshrq_n_u32(vec,N); } } } uint32x4_t 运算符>>(uint32x4_t const & vec, const int v) { return A::B::shiftRight(vec);并像这样使用它:uint32x4_t vec>>3;我得到了同样的错误:“uint32x4_t operator>>(const uint32x4_t&, int)' must have an argument of class or enumerated type uint32x4_t operator>>(uint32x4_t const & vec, const int v) {"
      • @Hymir 您不能将运行时值用作模板参数。摆脱模板,它对您没有帮助。
      • @RemyLebeau 我知道。它根本不是运行时值。
      猜你喜欢
      • 1970-01-01
      • 2011-02-02
      • 1970-01-01
      • 2012-10-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多