c ++ make嵌套元组“plain”/“flatten”

时间:2015-08-23 11:13:03

标签: c++ tuples

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

案例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<ids>( forward<Rec>(rec).data))...

情况2

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

2 个答案:

答案 0 :(得分:3)

#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 :(得分:3)

这是我的看法。

首先,一个包装器,它需要正确处理包含源元组和元素的引用和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