【问题标题】:c++ make nested tuple "plain" / "flatten"c++ 使嵌套元组“普通”/“扁平化”
【发布时间】:2015-08-23 11:13:03
【问题描述】:

问题: 使元组的元组“简单”的最佳方法是什么?例如。一维。

案例1

template <class Field>
auto process_field(Field&& field){
    // or another std::get<ids>(field)...
    return std::forward_as_tuple(field.fst, field.snd, field.thrd, field.fth); 
}

template <std::size_t ...ids, class Rec>
auto iterate_record(std::index_sequence<ids...>, Rec && rec){
    return forward_as_tuple(
        process_field
        (std::get<ids>( forward<Rec>(rec).data))...
    );
}

iterate_record 返回元组的元组。我需要简单的元组。

也许我需要某种围绕 iterate_record 的包装器,或者直接围绕它:process_field (std::get&lt;ids&gt;( forward&lt;Rec&gt;(rec).data))...


案例2

我有一个元组的元组(具体类型,而不是引用)。如何有效地将其复制到普通版本?

【问题讨论】:

  • tuple_cat 而不是 forward_as_tuple in iterate_record ?
  • @PiotrSkotnicki 请举例?
  • 深度 >2 是否可以有效地使用 tuple_cat ?
  • 如果process_field 返回一个值的元组(不是引用),您将无法不采取行动(或最坏情况下的副本)。否则你会有一个悬空引用的元组

标签: c++ tuples


【解决方案1】:

这是我的看法。

首先,一个包装器,它是正确处理包含源元组和元素上的引用和 cv 限定的元组所必需的。我们存储 1) 这个包装器应该解包到的类型;以及 2) 我们是否可以从源头移动。

template<class U, class T, bool can_move>
struct wrapper {
    T* ptr;
    wrapper(T& t) : ptr(std::addressof(t)) {}

    using unwrapped_type = 
        std::conditional_t<can_move, 
                           std::conditional_t<std::is_lvalue_reference<U>{}, T&, T&&>, 
                           std::conditional_t<std::is_rvalue_reference<U>{}, T&&, T&>>;
    using tuple_element_type = U;

    unwrapped_type unwrap() const{
        return std::forward<unwrapped_type>(*ptr);
    }
};

接下来,一个打开包装元组的函数:

template<class... Wrappers, std::size_t... Is>
auto unwrap_tuple(const std::tuple<Wrappers...>& t, std::index_sequence<Is...>) {
    return std::tuple<typename Wrappers::tuple_element_type...>(std::get<Is>(t).unwrap()...);
}

template<class... Wrappers>
auto unwrap_tuple(const std::tuple<Wrappers...>& t) {
    return unwrap_tuple(t, std::index_sequence_for<Wrappers...>());
}

现在将一个(可能是嵌套的)元组转换为一个扁平的包装元组。这与 Piotr 的 explode 非常相似:

template<bool can_move, class V, class T>
auto wrap_and_flatten(T& t, char){
    return std::make_tuple(wrapper<V, T, can_move>(t));
}
template<class T> struct is_tuple : std::false_type {};
template<class... Ts> struct is_tuple<std::tuple<Ts...>> : std::true_type {};
template<class T> struct is_tuple<const T> : is_tuple<T> {};
template<class T> struct is_tuple<volatile T> : is_tuple<T> {};

template<bool can_move, class, class Tuple,
         class = std::enable_if_t<is_tuple<std::decay_t<Tuple>>{}>>
auto wrap_and_flatten(Tuple& t, int);

template<bool can_move, class Tuple, std::size_t... Is>
auto wrap_and_flatten(Tuple& t, std::index_sequence<Is...>) {
    return std::tuple_cat(wrap_and_flatten<can_move, std::tuple_element_t<Is, std::remove_cv_t<Tuple>>>(std::get<Is>(t), 0)...);
}

template<bool can_move, class V, class Tuple, class>
auto wrap_and_flatten(Tuple& t, int) {
    using seq_type = std::make_index_sequence<std::tuple_size<Tuple>{}>;
    return wrap_and_flatten<can_move>(t, seq_type());
}

template<class Tuple>
auto wrap_and_flatten_tuple(Tuple&& t){
    constexpr bool can_move = !std::is_lvalue_reference<Tuple>{};
    using seq_type = std::make_index_sequence<std::tuple_size<std::decay_t<Tuple>>{}>;
    return wrap_and_flatten<can_move>(t, seq_type());
}

最后,将它们组合在一起 - 包裹、展平,然后展开:

template <typename T>
auto merge_tuple(T&& t)
{
    return unwrap_tuple(wrap_and_flatten_tuple(std::forward<T>(t)));
}

Demo.

【讨论】:

  • 是否也可以通过引用原件获得结果? :) 看起来很有帮助。
  • @tower120 所以所有的引用都没有价值?只需在unwrap_tuple 中使用unwrapped_type 而不是tuple_element_type
  • 你看到引用作为从嵌套到普通元组的“适配器”非常有用,当你只需要将它传递给函数时......换句话说,允许使用普通元组的函数(例如,找到最大/最小值),免费使用嵌套...所以我需要两者:) 参考原件和从原件复制。
  • 我以前从未见过std::addressof,使用起来安全吗?似乎它在里面做reinterpret_cast,据我了解,它具有实现定义的行为,因此可能不可移植。
  • @tower120 std::addressof 所做的很明确:“获取对象或函数arg 的实际地址,即使存在重载的operator&amp;”。无论是使用reinterpret_cast、编译器内在函数还是内部的巫术魔法都无关紧要;它必须按照指定的方式运行。
【解决方案2】:
#include <utility>
#include <tuple>
#include <cstddef>
#include <type_traits>

template <typename T>
auto explode(T&& t, char)
{
    return std::forward_as_tuple(std::forward<T>(t));
}

template <typename T, std::size_t I = std::tuple_size<std::decay_t<T>>{}>
auto explode(T&& t, int);

template <typename T, std::size_t... Is>
auto explode(T&& t, std::index_sequence<Is...>)
{
    return std::tuple_cat(explode(std::get<Is>(std::forward<T>(t)), 0)...);
}

template <typename T, std::size_t I>
auto explode(T&& t, int)
{
    return explode(std::forward<T>(t), std::make_index_sequence<I>{});
}

template <typename T, std::size_t... Is>
auto decay_tuple(T&& t, std::index_sequence<Is...>)
{
    return std::make_tuple(std::get<Is>(std::forward<T>(t))...);
}

template <typename T>
auto decay_tuple(T&& t)
{
    return decay_tuple(std::forward<T>(t), std::make_index_sequence<std::tuple_size<std::decay_t<T>>{}>{});
}

template <typename T, std::size_t... Is>
auto merge_tuple(T&& t, std::index_sequence<Is...>)
{
    return decay_tuple(std::tuple_cat(explode(std::get<Is>(std::forward<T>(t)), 0)...));
}

template <typename T>
auto merge_tuple(T&& t)
{
    return merge_tuple(std::forward<T>(t), std::make_index_sequence<std::tuple_size<std::decay_t<T>>{}>{});
}

测试:

int main()
{
    std::tuple<
               std::tuple<
                          std::tuple<
                                     std::tuple<Noisy, Noisy>
                                    >
                         >
             , std::tuple<
                          std::tuple<Noisy>
                         >
              > t;        

    auto x = merge_tuple(t);

    static_assert(std::is_same<decltype(x), std::tuple<Noisy, Noisy, Noisy>>{}, "!");
}

输出:

Noisy()
Noisy()
Noisy()
Noisy(const Noisy&)
Noisy(const Noisy&)
Noisy(const Noisy&)
~Noisy()
~Noisy()
~Noisy()
~Noisy()
~Noisy()
~Noisy()

DEMO

【讨论】:

  • 很好,但是 1) tuple_size 不一定对 SFINAE 友好,并且 2) 不适用于包含引用的元组?
  • 看起来像我的问题的答案。但是... :) 为什么复制构造函数只调用一次?您多次致电tuple_cat
  • @T.C.它适用于所有内容 - 只需在 merge_tuple 中删除/评论 decay_tuple。看coliru.stacked-crooked.com/a/664616268436454a。如果没有decay_tuple - 元组将包含对原始数组的引用(这对于我来说非常适合函数调用)[它实际上应该存储然后“恢复”原始状态,而不是只是衰减它]。所以我认为这里应该有两个功能 - 第一个用于合并(使用引用,零开销),第二个 - 用于实际复制(使用原始类型)。
  • @tower120 既不保留原始类型。给定std::tuple&lt;Noisy&amp;&amp;, Noisy&amp;, Noisy&gt; t = /* ... */;merge_tuple(t) 不是身份转换。
  • @T.C.我故意添加了decay_tuple 来强制复制所有元组元素,认为这是OP想要的,并且我没有考虑引用元组,我将不得不看看这个案例
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2023-04-08
  • 2017-03-19
  • 1970-01-01
  • 1970-01-01
  • 2014-08-12
  • 2019-04-24
  • 1970-01-01
相关资源
最近更新 更多