减少模板化可变参数函数模糊性的最佳方法是什么?

时间:2017-10-25 20:58:53

标签: c++ c++11 templates recursion variadic-templates

我目前正在尝试创建一个名为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...);
}

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

3 个答案:

答案 0 :(得分:1)

也许这是个人品味的问题,但我更愿意避免让两个版本的函数做同样的事情(在这种情况下: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_接收到三个或更多个参数,则会调用递归版本;如果收到两个参数,则称为最终版本。

答案 1 :(得分:1)

在你的情况下,有明显不同的方法,如

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

答案 2 :(得分:0)

我在Coding Den Discord服务器上通过eracpp使用fold expression制作了一个解决方案:

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