减少模板化变差函数歧义的最佳方法是什么

What is the best way to reduce ambiguity for templated variadic functions?

本文关键字:歧义 最佳 方法 是什么 函数      更新时间:2023-10-16

我目前正在尝试制作一个名为MakeByte的可变模板函数,该函数将接受任意数量的位参数,并将它们放入一个字节中。以下是它的用法示例:

// The Wii U has 4 RAM chips, here we select a seemingly "random" one using an
// algorithm to generate one from the coordinates.
quint32 bank_bit_0 = Bit((y / (16 * m_num_pipes)) ^ x_3);
quint32 bank_bit_1 = Bit((y / (8 * m_num_pipes)) ^ x_4);
quint32 bank = MakeByte(bank_bit_0, bank_bit_1);

我有三个功能,在一个单独的标题中涉及:

  • template <typename T1, typename... T2> T1 MakeByte(T1 bit, T2... bits),调用递归函数的外部代码将使用的函数
  • template <typename T1, typename... T2> T1 MakeByte(T1 byte, quint32 pos, T1 bit, T2... bits),对每个比特进行迭代的递归函数。这个函数有额外的参数来跟踪最后一个字节,以及放置下一个位的当前位置
  • template <typename T1, typename T2> T1 MakeByte(T1 byte, quint32 pos, T2 bit),处理最后一位的函数

以下是完整的3个定义:

template <typename T1, typename T2>
constexpr T1 MakeByte(T1 byte, quint32 pos, T2 bit)
{
return byte | (bit << pos);
}
template <typename T1, typename... T2>
constexpr T1 MakeByte(T1 byte, quint32 pos, T1 bit, T2... bits)
{
return MakeByte(byte | (bit << pos), pos + 1, bit, bits...);
}
template <typename T1, typename... T2>
constexpr T1 MakeByte(T1 bit, T2... bits)
{
return MakeByte(static_cast<T1>(0), 0, bit, bits...);
}

问题是,当使用g++编译时,我会得到以下错误:

/home/kyle/Documents/Projects/C++/Qt/MK8Studio/Source/Common.h:44: error: template instantiation depth exceeds maximum of 900 (use -ftemplate-depth= to increase the maximum)
return MakeByte(static_cast<T1>(0), 0, bit, bits...);
~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

在这一点上,我重命名了两个递归函数,以防出现歧义:

template <typename T1, typename T2>
constexpr T1 MakeByte_(T1 byte, quint32 pos, T2 bit)
{
return byte | (bit << pos);
}
template <typename T1, typename... T2>
constexpr T1 MakeByte_(T1 byte, quint32 pos, T1 bit, T2... bits)
{
return MakeByte_(byte | (bit << pos), pos + 1, bit, bits...);
}
template <typename T1, typename... T2>
constexpr T1 MakeByte(T1 bit, T2... bits)
{
return MakeByte_(static_cast<T1>(0), 0, bit, bits...);
}

这段代码确实可以编译,但我忍不住觉得这有点像黑客。从设计的角度来看,减少可变模板函数中歧义的最佳方法是什么?

也许这是个人品味的问题,但我更喜欢避免有两个版本的函数在做同样的事情(在这种情况下:shift和or)。

我建议写一个终端案例,简单地返回一个值(无偏移,无或)

template <typename T1>
constexpr T1 MakeByte_ (T1 byte, quint32)
{ return byte; }

和递归情况

template <typename T1, typename... T2>
constexpr T1 MakeByte_(T1 byte, quint32 pos, T1 bit, T2... bits)
{ return MakeByte_(byte | (bit << pos), pos + 1, bit, bits...); }

这避免了歧义,因为如果MakeByte_接收到三个或多个参数,则调用递归版本;如果接收到两个参数,则称为最终版本。

在您的案例中,有明显不同的方法,如

quint32 bank_bit_0 = Bit((y / (16 * m_num_pipes)) ^ x_3);
quint32 bank_bit_1 = Bit((y / (8 * m_num_pipes)) ^ x_4);
quint32 bank_bit_2 = Bit((y / (m_num_pipes)) ^ x_5);
quint32 bank = MakeByte(bank_bit_0, bank_bit_1, bank_bit_1);

会调用你的助手函数

template <typename T1, typename T2>
constexpr T1 MakeByte(T1 byte, quint32 pos, T2 bit)

注意:在C++17中,您甚至可以编写代码:

template <typename Tuple, std::size_t ... Is>
constexpr auto MakeByte_(const Tuple& tuple, std::index_sequence<Is...>)
{
return ((std::get<Is>(tuple) << Is) | ...) ;
}
template <typename T1, typename... T2>
constexpr T1 MakeByte(T1 bit, T2... bits)
{
return MakeByteHelper(std::make_tuple(bit, bits...),
std::make_index_sequence<1 + sizeof...(T2)>());
}

多亏了Codeing Den Discord服务器上的eracpp,我使用了一个折叠表达式:

template <typename T1, typename... T2>
constexpr T1 MakeByte(T1 bit, T2... bits)
{
T1 byte = bit;
quint32 pos = 0;
((byte |= ((bits & 1) << ++pos)), ...);
return byte;
}

这将代码缩减为一个函数,但确实需要C++17。这将从左到右的参数作为LSB到MSB,但这可以用于从左到右侧作为MSB到LSB(与二进制布局相关):

template <typename T1, typename... T2>
constexpr T1 MakeByte(T1 bit, T2... bits) {
uint32_t pos  = sizeof...(bits);
T1      byte = bit;
((byte = (byte << 1) | bits), ...);
return byte;
}

最后,他们提出了一个我还没有测试过的非C++17解决方案:

template <typename T1, typename... T2>
constexpr T1 MakeByte(T1 bit, T2... bits) {
T1 byte = bit;
using init_t = int[];
(void)init_t{((byte = (byte << 1) | bits), 0)...};
return byte;
}