如何反转元组类型中元素类型的顺序?

问题描述 投票:0回答:7

如何反转元组中的类型?例如,我希望

reverse_tuple<std::tuple<int, char, bool>>::type
成为
std::tuple<bool, char, int>
。我尝试执行以下操作,但没有成功。我做错了什么?

#include <type_traits>
#include <tuple>

template <typename... Ts>
struct tuple_reverse;

template <typename T, typename... Ts>
struct tuple_reverse<std::tuple<T, Ts...>>
{
    using type = typename tuple_reverse<
                            std::tuple<
                               typename tuple_reverse<std::tuple<Ts..., T>>::type
                            >
                          >::type;
};

template <typename T>
struct tuple_reverse<std::tuple<T>>
{
    using type = std::tuple<T>;
};

int main()
{
    using result_type = std::tuple<int, bool, char>;
    static_assert(
        std::is_same<
            tuple_reverse<var>::type, std::tuple<char, bool, int>
        >::value, ""
    );
}

这是我的错误:

prog.cpp: In instantiation of ‘struct tuple_reverse<std::tuple<char, int, bool> >’:

prog.cpp:15:34:   recursively required from ‘struct tuple_reverse<std::tuple<bool, char, int> >’

prog.cpp:15:34:   required from ‘struct tuple_reverse<std::tuple<int, bool, char> >’

prog.cpp:29:31:   required from here

prog.cpp:15:34: error: no type named ‘type’ in ‘struct tuple_reverse<std::tuple<int, bool, char> >’

prog.cpp: In function ‘int main()’:

prog.cpp:30:9: error: template argument 1 is invalid

c++ c++11 tuples variadic-templates
7个回答
23
投票

你做错的地方在这里:

using type = typename tuple_reverse<
                        std::tuple<
                           typename tuple_reverse<std::tuple<Ts..., T>>::type
                        >
                      >::type;

从内到外看它,你重新排序元组元素:

tuple<Ts..., T>
,然后你尝试反转它,然后你将结果放入
tuple
,然后你尝试反转that......嗯? ! :)

这意味着每次实例化

tuple_reverse
时,您都会给它一个相同大小的元组,因此它永远不会完成,并且永远递归地实例化自身。 (然后,如果递归甚至完成,您将生成的元组类型放入一个元组中,这样您就有一个包含 N 元素元组的单元素元组,并将其反转,这不会执行任何操作,因为反转单元素元组是无操作。)

您想要剥离其中一个元素,然后反转其余元素,然后再次将其连接回去:

using head = std::tuple<T>;
using tail = typename tuple_reverse<std::tuple<Ts...>>::type;

using type = decltype(std::tuple_cat(std::declval<tail>(), std::declval<head>()));

并且您不需要将其包装在元组中并再次反转:)

你还应该处理空元组的情况,所以整个事情是:

template <typename... Ts>
struct tuple_reverse;

template <>
struct tuple_reverse<std::tuple<>>
{
    using type = std::tuple<>;
};

template <typename T, typename... Ts>
struct tuple_reverse<std::tuple<T, Ts...>>
{
  using head = std::tuple<T>;
  using tail = typename tuple_reverse<std::tuple<Ts...>>::type;

  using type = decltype(std::tuple_cat(std::declval<tail>(), std::declval<head>()));
};

但我会采取不同的做法。

要仅获取类型,请使用 C++14

template<typename T, size_t... I>
struct tuple_reverse_impl<T, std::index_sequence<I...>>
{
  typedef std::tuple<typename std::tuple_element<sizeof...(I) - 1 - I, T>::type...> type;
};

// partial specialization for handling empty tuples:
template<typename T>
struct tuple_reverse_impl<T, std::index_sequence<>>
{
  typedef T type;
};

template<typename T>
struct tuple_reverse<T>
: tuple_reverse_impl<T, std::make_index_sequence<std::tuple_size<T>::value>>
{ };

或者您可以编写一个函数来反转实际的元组对象,然后使用

decltype(reverse(t))
来获取类型。在 C++14 中反转类似元组的对象:

template<typename T, size_t... I>
auto
reverse_impl(T&& t, std::index_sequence<I...>)
{
  return std::make_tuple(std::get<sizeof...(I) - 1 - I>(std::forward<T>(t))...);
}

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

在 C++11 中,使用

<integer_seq.h>
并添加返回类型,并使用
remove_reference
从元组类型中删除引用(因为
tuple_size
tuple_element
不适用于对元组的引用):

template<typename T, typename TT = typename std::remove_reference<T>::type, size_t... I>
auto
reverse_impl(T&& t, redi::index_sequence<I...>)
-> std::tuple<typename std::tuple_element<sizeof...(I) - 1 - I, TT>::type...>
{
    return std::make_tuple(std::get<sizeof...(I) - 1 - I>(std::forward<T>(t))...);
}

template<typename T, typename TT = typename std::remove_reference<T>::type>
auto
reverse(T&& t)
-> decltype(reverse_impl(std::forward<T>(t),
                        redi::make_index_sequence<std::tuple_size<TT>::value>()))
{
    return reverse_impl(std::forward<T>(t),
                        redi::make_index_sequence<std::tuple_size<TT>::value>());
}

8
投票

未经测试。

template < typename Tuple, typename T >
struct tuple_push;

template < typename T, typename ... Args >
struct tuple_push<std::tuple<Args...>, T>
{
    typedef std::tuple<Args...,T> type;
};

template < typename Tuple >
struct tuple_reverse;

template < typename T, typename ... Args >
struct tuple_reverse<std::tuple<T, Args...>>
{
    typedef typename tuple_push<typename tuple_reverse<std::tuple<Args...>>::type, T>::type type;
};

template < >
struct tuple_reverse<std::tuple<>>
{
    typedef std::tuple<> type;
};

无论如何,总有一些事情。

这也只会颠倒类型,这似乎就是您所追求的。反转实际元组将涉及函数,而不是元函数。


3
投票

我在处理任意类型的反转模板参数时遇到了这个问题。

Jonathan Wakely 的答案对于元组非常有效,但如果其他人需要反转任何类型,即

T<P1, P2, ..., Pn>
T<Pn, Pn-1, ..., P1>
,这就是我想到的(取自此处的反转逻辑)。

namespace Details
{
    /// Get the base case template type `T<>` of a templated type `T<...>`
    template<typename>
    struct templated_base_case;

    template <template<typename...> class T, typename... TArgs>
    struct templated_base_case<T<TArgs...>>
    {
        using type = T<>;
    };

    /// Inner reverse logic.
    ///
    /// Reverses the template parameters of a templated type `T` such
    /// that `T<A, B, C>` becomes `T<C, B, A>`.
    ///
    /// Note that this requires `T<>` to exist.
    template<
        typename T,
        typename = typename templated_base_case<T>::type>
    struct reverse_impl;

    template<
        template <typename...> class T,
        typename... TArgs>
    struct reverse_impl<
        typename templated_base_case<T<TArgs...>>::type,
        T<TArgs...>>
    {
        using type = T<TArgs...>;
    };

    template<
        template<typename...> class T,
        typename first,
        typename... rest,
        typename... done>
    struct reverse_impl<
        T<first, rest...>,
        T<done...>>
    {
        using type = typename reverse_impl <T<rest...>, T<first, done...>>::type;
    };

    /// Swap template parameters of two templated types.
    ///
    /// `L<A, B, C> and R<X, Y, Z>` become `L<X, Y, Z> and R<A, B, C>`.
    template<typename L, typename R>
    struct swap_template_parameters;

    template<
        template<typename...> class L,
        template<typename...> class R,
        typename... x,
        typename... y>
    struct swap_template_parameters<L<x...>, R<y...>>
    {
        using left_type = L<y...>;
        using right_type = R<x...>;
    };
}

/// Parameter pack list of types
template <typename... Args>
struct type_list { };

/// Reverses the arguments of a templates type `T`.
///
/// This uses a `type_list` to allow reversing types like std::pair
/// where `std::pair<>` and `std::pair<T>` are not valid.
template<typename T>
struct reverse_type;

template<template<typename...> class T, typename... TArgs>
struct reverse_type<T<TArgs...>>
{
    using type = typename Details::swap_template_parameters<
        T<TArgs...>,
        typename Details::reverse_impl<type_list<TArgs...>>::type>::left_type;
};

有些实现逻辑是可以组合的,但我在这里尽量说得清楚。

reverse_type
可以应用于元组:

using my_tuple = std::tuple<int, bool, char>;

static_assert(
    std::is_same<
        typename reverse_type<my_typle>::type,
        std::tuple<char, bool, int>>::value,
    "");

或其他类型:

/// Standard collections cannot be directly reversed easily
/// because they take default template parameters such as Allocator.
template<typename K, typename V>
struct simple_map : std::unordered_map<K, V> { };

static_assert(
    std::is_same<
        typename reverse_type<simple_map<std::string, int>>::type,
        simple_map<int, std::string>>::value,
    "");

稍微详细一点的解释.


1
投票

我结合了Faheem Mitha的 SequenceHelperJonathan Wakely's tuple_reverse 也可以反转元组类型和数据。该解决方案适用于 c++11。

#include <tuple>
#include <algorithm>
#include <vector>

namespace TupleHelper {

namespace SequenceHelper {

// Tuple to parameter pack
// Original Author: Faheem Mitha https://stackoverflow.com/users/350713/faheem-mitha
// https://stackoverflow.com/questions/36612596/tuple-to-parameter-pack
//
// License: Creative Commons Attribution-ShareAlike (CC-BY-SA)
// https://www.ictrecht.nl/en/blog/what-is-the-license-status-of-stackoverflow-code-snippets

template<int ...>
struct seq { };

template<int N, int ...S>
struct gens : gens<N-1, N-1, S...> {};

template<int ...S>
struct gens<0, S...> {
    typedef seq<S...> type;

#if __GNUC__
    virtual ~gens() {} // this is only to avoid -Weffc++ warning
#endif
};

// end of Faheem Mitha's code

} // namespace SequenceHelper

// reverses the tuple types
// How do I reverse the order of element types in a tuple type?
// Original Author: Jonathan Wakely https://stackoverflow.com/users/981959/jonathan-wakely
// https://stackoverflow.com/a/17178399
//
// License: Creative Commons Attribution-ShareAlike (CC-BY-SA)
// https://www.ictrecht.nl/en/blog/what-is-the-license-status-of-stackoverflow-code-snippets

template <typename... Ts>
struct tuple_reverse;

template <>
struct tuple_reverse<std::tuple<>>
{
    using type = std::tuple<>;
};

template <typename T, typename... Ts>
struct tuple_reverse<std::tuple<T, Ts...>>
{
  using head = std::tuple<T>;
  using tail = typename tuple_reverse<std::tuple<Ts...>>::type;

  using type = decltype(std::tuple_cat(std::declval<tail>(), std::declval<head>()));
};

// end of Jonathan Wakely's code

// reverses the content of the tuple too
namespace TupleReverseImpl {


// Helper functions to recursivly copy the source tuple to the target tuple
template <class ...Args>
void doCopyTypes( std::vector<void*>::size_type index,
                  std::vector<const void*> & content_source,
                  std::vector<void*> & content_target ) {}

template <class T, class ...Args>
void doCopyTypes( std::vector<void*>::size_type index,
                  std::vector<const void*> & content_source,
                  std::vector<void*> & content_target,
                  const T * t,
                  Args... args )
{
  // target and source vector do have the types in the same order
  // so we can cast here to the correct type, then copy it
  T * ptrSource = reinterpret_cast<T*>(const_cast<void*>(content_source.at(index)));
  T * ptrTarget = reinterpret_cast<T*>(content_target.at(index));

  *ptrTarget = *ptrSource;

  doCopyTypes( index+1, content_source, content_target, args... );
}

template <class Tuple, int ...S>
void copyTypes( std::vector<const void*> & content_source,
                std::vector<void*> & content_target,
                Tuple & tuple,
                SequenceHelper::seq<S...> )
{
  doCopyTypes( 0, content_source, content_target, &std::get<S>(tuple)... );
}

// Helper functions to fill a vector of pointers, to prepare copying

template <class V>
void dofillContent( V & content ) {}

template <class V, class T, class ...Args>
void dofillContent( V & content, T * t, Args... args )
{
  content.push_back( t );

  dofillContent( content, args... );
}

template <class V,class Tuple, int ...S>
void fill( V & content, Tuple & tuple, SequenceHelper::seq<S...> )
{
  dofillContent( content, &std::get<S>(tuple)... );
}

} // namespace TupleReverseImpl

/*
 * this reverses a tuple and its content
 *
 * std::tuple<int,bool,std::string> reverse = TupleHelper::reverseTuple( std::make_tuple( std::string( "one", true, 42 ) ) );
 */
template <class Tuple>
typename tuple_reverse<Tuple>::type reverseTuple( const Tuple & tuple )
{
    // declare return type
    typedef typename tuple_reverse<Tuple>::type REVERSE_TUPLE_TYPE;
    REVERSE_TUPLE_TYPE return_tuple;

    // create source and target pointer vectors for the copy action
    std::vector<const void*> contentSource;
    std::vector<void*> contentTarget;

    TupleReverseImpl::fill( contentSource, tuple, typename SequenceHelper::gens<std::tuple_size<Tuple>::value>::type() );
    TupleReverseImpl::fill( contentTarget, return_tuple, typename SequenceHelper::gens<std::tuple_size<REVERSE_TUPLE_TYPE>::value>::type() );

    // to be in the same order as contentTarget
    std::reverse(contentTarget.begin(), contentTarget.end() );


    // now copy everything
    TupleReverseImpl::copyTypes( contentSource, contentTarget, tuple, typename SequenceHelper::gens<std::tuple_size<Tuple>::value>::type() );

    return return_tuple;
}

} // namespace TupleHelper

int main()
{
    std::tuple<int,bool,std::string> reverse = TupleHelper::reverseTuple( std::make_tuple( std::string( "one", true, 42 ) ) );
}

0
投票

出于兴趣,您是否真的想反转元组类型,或者只是以相反的顺序处理每个元素(就像我的项目中更常见的情况)?

#include <utility>
#include <tuple>
#include <iostream>

namespace detail {

    template<class F, class Tuple, std::size_t...Is>
    auto invoke_over_tuple(F &&f, Tuple &&tuple, std::index_sequence<Is...>) {
        using expand = int[];
        void(expand{0,
                    ((f(std::get<Is>(std::forward<Tuple>(tuple)))), 0)...});
    }


    template<class Sequence, std::size_t I>
    struct append;
    template<std::size_t I, std::size_t...Is>
    struct append<std::index_sequence<Is...>, I> {
        using result = std::index_sequence<Is..., I>;
    };

    template<class Sequence>
    struct reverse;

    template<>
    struct reverse<std::index_sequence<>> {
        using type = std::index_sequence<>;
    };

    template<std::size_t I, std::size_t...Is>
    struct reverse<std::index_sequence<I, Is...>> {
        using subset = typename reverse<std::index_sequence<Is...>>::type;
        using type = typename append<subset, I>::result;
    };
}

template<class Sequence>
using reverse = typename detail::reverse<Sequence>::type;

template
        <
                class Tuple,
                class F
        >
auto forward_over_tuple(F &&f, Tuple &&tuple) {
    using tuple_type = std::decay_t<Tuple>;
    constexpr auto size = std::tuple_size<tuple_type>::value;
    return detail::invoke_over_tuple(std::forward<F>(f),
                                     std::forward<Tuple>(tuple),
                                     std::make_index_sequence<size>());
};

template
        <
                class Tuple,
                class F
        >
auto reverse_over_tuple(F &&f, Tuple &&tuple) {
    using tuple_type = std::decay_t<Tuple>;
    constexpr auto size = std::tuple_size<tuple_type>::value;
    return detail::invoke_over_tuple(std::forward<F>(f),
                                     std::forward<Tuple>(tuple),
                                     reverse<std::make_index_sequence<size>>());
};

int main()
{
    auto t = std::make_tuple("1", 2, 3.3, 4.4, 5, 6, "7");
    forward_over_tuple([](auto &&x) { std::cout << x << " "; }, t);
    std::cout << std::endl;

    reverse_over_tuple([](auto &&x) { std::cout << x << " "; }, t);
    std::cout << std::endl;
}

0
投票
#include <tuple>

template<typename T>
struct Reverse;

template<typename... Ts>
struct Reverse<std::tuple<Ts...>>
{
    using type = std::tuple<typename std::tuple_element<sizeof...(Ts) - 1 - i, std::tuple<Ts...>>::type...>;
};

// usage example
static_assert(std::is_same_v<typename Reverse<std::tuple<int, bool, double>>::type, std::tuple<double, bool, int>>);
using this method you can reverse the tuple type.

0
投票

我提供的这个版本几乎是如何在 Haskell 中反转列表的直接翻译。

这种方法简短、直接,不需要额外的帮助者。参数包

A...
充当累加器来收集结果 - 这是一种处理不可变列表的流行技术。


template <typename A, typename... B>
struct Reverse
{
};

template<template<typename...> typename TList, typename... A>
struct Reverse<TList<>, A...>
{
    using Type = TList<A...>;
};

template<template<typename...> typename TList, typename M, typename...N, typename... A>
struct Reverse<TList<M, N...>, A...>
{
    using Type = typename Reverse<TList<N...>, M, A...>::Type;
};

int main()
{
    static_assert(
        std::is_same<
            Reverse<std::tuple<char, int, bool>>::Type,
            std::tuple<bool, int, char>
            >::value,
        "Error"
        );
}```
© www.soinside.com 2019 - 2024. All rights reserved.