【问题标题】:How to remove the nth element from a tuple?如何从元组中删除第 n 个元素?
【发布时间】:2020-02-18 21:06:11
【问题描述】:

我正在尝试编写一个函数,该函数从现有的std::tuple 创建一个新的std::tuple,并跳过给定索引上的元素。例如:

我有一个元组 t 定义如下:

constexpr auto t = std::tuple(1, 2, 3, 4);

我想将它复制到另一个元组。但是,我想跳过 nth 元素。假设在这种情况下,我想跳过的 nth 元素是 3(这意味着我想跳过索引为 2 的元素)。这将导致一个新的元组定义为:

std::tuple(1, 2, 4);

这是迄今为止我得到的最接近的:

template<std::size_t N, typename T, std::size_t ... is>
constexpr auto fun(T&& tp, std::index_sequence<is...>&& i) noexcept {
    return std::tuple((is != N ? std::get<is>(tp) : 0) ...);
}

template<std::size_t N, std::size_t... elems>
constexpr auto fun2() noexcept {
    constexpr auto t = std::tuple(elems...);
    return fun<N>(std::forward_as_tuple(elems...), std::make_index_sequence<sizeof...(elems)>());
}

但是,我没有删除 nth 元素,而是将其设置为 0。

理想情况下,我会更改函数fun() 中的返回参数,以使用多个临时元组创建一个新元组:

return std::tuple_cat((is != N ? std::tuple(std::get<is>(tp)) : std::tuple()) ...);

但是,这个问题是三元运算符必须在两边都有匹配的类型。

我尝试的另一种方法是基于递归:

template<std::size_t N, std::size_t head, std::size_t... tail>
constexpr auto fun3() noexcept {
    if constexpr(!sizeof...(tail))
        return std::tuple(head);

    if constexpr(sizeof...(tail) - 1 == N)
        return std::tuple_cat(fun3<N, tail...>());

    if constexpr(sizeof...(tail) - 1 != N)
        return std::tuple_cat(std::tuple(head), fun3<N, tail...>());
}

然而,这更加不成功。在这种情况下,如果 N 等于 0,则 nth 元素(也是这里的第一个元素)仍将用于新元组中。此外,这甚至不会编译,因为第二个语句存在问题:

if constexpr(sizeof...(tail) - 1 == N)

我在这里缺少什么?如何复制元组并在复制过程中跳过其中一个元素?

我使用的是 C++17,我需要在编译时评估函数。

【问题讨论】:

    标签: algorithm templates c++17 variadic-templates template-meta-programming


    【解决方案1】:

    怎么样

    return std::tuple_cat( foo<is, N>::func(std::get<is>(tp)) ...);
    

    foo 是一个具有如下特化的结构体?

    template <std::size_t, std::size_t>
    struct foo
     {
       template <typename T>
       static auto func (T const & t)
        { return std::make_tuple(t); } 
     }
    
    template <std::size_t N>
    struct foo<N, N>
     {
       template <typename T>
       static std::tuple<> func (T const &)
        { return {}; } 
     }
    

    (注意:代码未经测试)。

    这几乎是你的三元运算符的想法,但没有匹配两边类型的问题:只实例化正确的类型。

    【讨论】:

      【解决方案2】:

      另一种解决方案是创建两个索引序列,它们引用元组的前后部分。

      template<std::size_t nth, std::size_t... Head, std::size_t... Tail, typename... Types>
      constexpr auto remove_nth_element_impl(std::index_sequence<Head...>, std::index_sequence<Tail...>, std::tuple<Types...> const& tup) {
          return std::tuple{
              std::get<Head>(tup)...,
              // We +1 to refer one element after the one removed 
              std::get<Tail + nth + 1>(tup)...
          };
      }
      
      template<std::size_t nth, typename... Types>
      constexpr auto remove_nth_element(std::tuple<Types...> const& tup) {
          return remove_nth_element_impl<nth>(
              std::make_index_sequence<nth>(), // We -1 to drop one element 
              std::make_index_sequence<sizeof...(Types) - nth - 1>(),
              tup
          );
      }
      

      这是对这个函数的测试:

      int main() {
          constexpr auto tup = std::tuple{1, 1.2, 'c'};
          constexpr auto tup2 = remove_nth_element<0>(tup);
          constexpr auto tup3 = remove_nth_element<2>(tup);
          static_assert(std::is_same_v<decltype(tup2), const std::tuple<double, char>>);
          static_assert(std::is_same_v<decltype(tup3), const std::tuple<int, double>>);
          return 0;
      }
      

      Live example

      此解决方案的优点是不构造中间元组和不使用std::tuple_cat,两者都可能在编译时间上很困难。

      【讨论】:

        【解决方案3】:

        在发布问题几分钟后,我找到了解决方法。这并不理想,但是嘿:

        template<std::size_t N, typename T, std::size_t ... is>
        constexpr auto fun(T&& tp, std::index_sequence<is...>&& i) noexcept {
            return std::tuple((is < N ? std::get<is>(tp) : std::get<is+1>(tp)) ...);
        }
        
        template<std::size_t N, std::size_t... elems>
        constexpr auto fun2() noexcept {
            constexpr auto t = std::tuple(elems...);
            return fun<N>(std::forward_as_tuple(elems...), std::make_index_sequence<sizeof... (elems) - 1>());
        }
        

        这样,我们复制了 nth 元素之前的所有元素,当我们到达 nth 元素时,我们将每个下一个索引增加 1。我们赢了不要超出范围,因为我们传递的 index_sequence 比传递的元组少 1 个元素。

        我希望这个答案对某人有所帮助。

        【讨论】:

        • Uhmmm... 之所以有效,是因为您有一个具有相同类型元素的std::tuple,因此std::get&lt;is&gt;(tp)std::get&lt;is+1&gt;(tp) 属于同一类型。通用 std::tuple 并非如此,并且使用 std::tuple_cat() 解决方案的三元运算符会出现相同的错误:具有不同类型的三元运算符。但是,也许,您可以从索引的角度应用此解决方案:std::tuple( std::get&lt;( is &lt; N ? is : is+1 )&gt;(tp) ) ...);
        • 是的,我需要它用于所有元素都具有相同类型的用例。但是您的解决方案要好得多,它适用于通用 std::tuple!只是一个注释;你在std::get&lt;(之后又漏了一个括号应该是std::tuple((std::get&lt;( is &lt; N ? is : is+1 )&gt;(tp) ) ...);
        • 你说对了吗:括号错误。也许您可以删除:std::tuple( std::get&lt;( is &lt; N ? is : is+1 )&gt;(tp) ...);。无论如何:如果你有一个包含所有相同类型元素的元组,为什么不使用std::array
        • 啊,我又错了,对不起!我正在用折叠表达式做其他事情,并开始认为我需要比我做的更多的括号。关于std::array,我认为我可以将它们用于我想要实现的目标。我不太确定为什么我一开始不这样做。这会让我的生活更轻松哈哈。但这也不算太糟糕。如果我决定扩展功能或将元组用于其他用途,那么我将拥有一个不错的基础。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-04-01
        • 2011-07-14
        • 1970-01-01
        相关资源
        最近更新 更多