【问题标题】:What is the best way to reduce ambiguity for templated variadic functions?减少模板化的变形功能模糊的最佳方法是什么?
【发布时间】:2018-04-07 02:32:28
【问题描述】:

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

这段代码确实可以编译,但我不禁觉得这有点骇人听闻。从设计的角度来看,在可变参数模板函数中减少歧义的最佳方法是什么?

【问题讨论】:

  • 没有消除歧义的通用“最佳方法”。每个人的情况都会根据自己的优点来解决。单独的解决方案可能涉及以下技术:重命名;使用std::enable_if;或作为部分特化模板函数的变通方法调用的部分特化模板类。没有一种尺寸适合所有人。
  • 什么是quint32?这很重要,因为我怀疑 int 可能是您的问题。
  • @Yakk 它只是一个无符号整数的 typedef。

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


【解决方案1】:

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

我建议写一个只返回一个值的终端案例(无移位,无或)

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_ 接收三个或更多参数,则调用递归版本;如果接收到两个参数,则称为最终版本。

【讨论】:

  • 除非我理解错了,否则我认为递归函数和外部代码要使用的函数之间仍然存在歧义。
  • @Koopa - 你还没有解决这个命名辅助函数 MakeByte_ 的歧义吗?
【解决方案2】:

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

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

【讨论】:

    【解决方案3】:

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

    【讨论】:

    • 你的非 c++17 是 C++14(c++11 对 constexpr 函数的限制太严格了)。
    • ((byte = (byte &lt;&lt; 1) | bits), ...); 中的bits 是故意的,它指的是参数包,除非您在谈论其他内容。
    猜你喜欢
    • 2012-01-08
    • 1970-01-01
    • 2015-08-21
    • 2015-02-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多