参数化和“不允许函数模板部分特化”

4
这是什么是constexpr的函数参数等效形式?的延续。在原问题中,我们试图加速一些在Clang和VC++下执行移位和旋转的代码。由于Clang和VC++将移位/旋转量视为变量(即不是constexpr),它们不能对代码进行良好的优化。
当我尝试参数化移位量和字长时,结果为:
$ g++ -std=c++11 -march=native test.cxx -o test.exe
test.cxx:13:10: error: function template partial specialization is not allowed
uint32_t LeftRotate<uint32_t, unsigned int>(uint32_t v)
         ^         ~~~~~~~~~~~~~~~~~~~~~~~~
test.cxx:21:10: error: function template partial specialization is not allowed
uint64_t LeftRotate<uint64_t, unsigned int>(uint64_t v)
         ^         ~~~~~~~~~~~~~~~~~~~~~~~~
2 errors generated.

这是测试程序。它比需要的稍微大一点,以便人们可以看到我们需要处理 uint32_tuint64_t> (更不用说 uint8_tuint16_t 和其他类型了)。
$ cat test.cxx
#include <iostream>
#include <stdint.h>

template<typename T, unsigned int R>
inline T LeftRotate(unsigned int v)
{
  static const unsigned int THIS_SIZE = sizeof(T)*8;
  static const unsigned int MASK = THIS_SIZE-1;
  return T((v<<R)|(v>>(-R&MASK)));
};

template<uint32_t, unsigned int R>
uint32_t LeftRotate<uint32_t, unsigned int>(uint32_t v)
{
  __asm__ ("roll %1, %0" : "+mq" (v) : "I" ((unsigned char)R));
  return v;
}

#if __x86_64__
template<uint64_t, unsigned int R>
uint64_t LeftRotate<uint64_t, unsigned int>(uint64_t v)
{
  __asm__ ("rolq %1, %0" : "+mq" (v) : "J" ((unsigned char)R));
  return v;
}
#endif

int main(int argc, char* argv[])
{
  std::cout << "Rotated: " << LeftRotate<uint32_t, 2>((uint32_t)argc) << std::endl;
  return 0;
}

我曾根据旋转实现的方式经历了多次错误消息迭代。其他错误消息包括no function template matches function template specialization...。使用template <>似乎产生了最难以理解的一种错误消息。
我该如何参数化移位量,以便Clang和VC++可以按预期优化函数调用?
2个回答

3

另一种方法是将模板化的常量转换为编译器可以优化的常量参数。

步骤1:定义旋转距离的概念:

template<unsigned int R> using rotate_distance = std::integral_constant<unsigned int, R>;

步骤2:根据一个接受此类型参数的函数的重载来定义旋转函数。
template<unsigned int R>
uint32_t LeftRotate(uint32_t v, rotate_distance<R>)

现在,如果我们愿意,我们可以简单地调用LeftRotate(x, rotate_distance<y>()),这似乎很好地表达了意图,或者我们现在可以重新定义基于此形式的2参数模板形式。
template<unsigned int Dist, class T>
T LeftRotate(T t)
{
  return LeftRotate(t, rotate_distance<Dist>());
}

完整演示:
#include <iostream>
#include <stdint.h>
#include <utility>

template<unsigned int R> using rotate_distance = std::integral_constant<unsigned int, R>;

template<typename T, unsigned int R>
inline T LeftRotate(unsigned int v, rotate_distance<R>)
{
  static const unsigned int THIS_SIZE = sizeof(T)*8;
  static const unsigned int MASK = THIS_SIZE-1;
  return T((v<<R)|(v>>(-R&MASK)));
}

template<unsigned int R>
uint32_t LeftRotate(uint32_t v, rotate_distance<R>)
{
  __asm__ ("roll %1, %0" : "+mq" (v) : "I" ((unsigned char)R));
  return v;
}

#if __x86_64__
template<unsigned int R>
uint64_t LeftRotate(uint64_t v, rotate_distance<R>)
{
  __asm__ ("rolq %1, %0" : "+mq" (v) : "J" ((unsigned char)R));
  return v;
}
#endif


template<unsigned int Dist, class T>
T LeftRotate(T t)
{
  return LeftRotate(t, rotate_distance<Dist>());
}

int main(int argc, char* argv[])
{
  std::cout << "Rotated: " << LeftRotate((uint32_t)argc, rotate_distance<2>()) << std::endl;
  std::cout << "Rotated: " << LeftRotate((uint64_t)argc, rotate_distance<2>()) << std::endl;
  std::cout << "Rotated: " << LeftRotate<2>((uint64_t)argc) << std::endl;
  return 0;
}

pre-c++11编译器

在c++11之前,我们没有std::integral_constant,因此需要自己创建版本。

对于我们的目的,以下内容已足够:

template<unsigned int R> struct rotate_distance {};

完美的证明-请注意优化的效果:

https://godbolt.org/g/p4tsQ5


谢谢Richard。这对我们可能有用。痛点在于从清洁室转移到生产代码。这个特定的移位/旋转代码已经稳定了大约20年,所以我必须小心地推动多少(特别是在旧编译器上,如GCC 3和VC++ 2002)。 - jww
@jww 我认为你会发现生成的汇编代码在优化后是完全相同的。这只是表达意图的不同方式。 - Richard Hodges
@jww 注意在2个参数形式中模板参数顺序的颠倒。这样可以推导出第二个模板参数,同时固定第一个模板参数。 - Richard Hodges
再次感谢Richard。我为"immediate rotate"指令添加了一些代码生成测试。由于集成汇编器,我看到了一些潜在的痛点。我也没有看到支持BMI处理器的rorx生成。接下来是VC++生成,看看情况如何。 - jww

1
使用模板类而不是模板函数:
#include <iostream>
#include <stdint.h>


template<typename T, unsigned int R>
struct LeftRotate {
    static inline T compute(T v)
    {
        static const unsigned int THIS_SIZE = sizeof(T)*8;
        static const unsigned int MASK = THIS_SIZE-1;
        return T((v<<R)|(v>>(-R&MASK)));
    }
};


template<unsigned int R>
struct LeftRotate<uint32_t, R> {
    static inline uint32_t compute(uint32_t v)
    {
        __asm__ ("roll %1, %0" : "+mq" (v) : "I" ((unsigned char)R));
        return v;
    }
};

#if __x86_64__
template<unsigned int R>
struct LeftRotate<uint64_t, R> {
    static inline uint64_t compute(uint64_t v)
    {
        __asm__ ("rolq %1, %0" : "+mq" (v) : "J" ((unsigned char)R));
        return v;
    }
};
#endif

int main(int argc, char* argv[])
{
  std::cout << "Rotated: " << LeftRotate<uint32_t, 2>::compute((uint32_t)argc) << std::endl;
  return 0;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接