删除第一种类型的std :: tuple

时间:2013-02-13 11:33:44

标签: c++ c++11 stdtuple

这似乎是一个非常简单的问题:如何删除std::tuple中的第一个(第n个)类型?

示例:

typedef std::tuple<int, short, double> tuple1;
typedef std::tuple<short, double> tuple2;

上述操作会将tuple1转换为tuple2。有可能吗?

5 个答案:

答案 0 :(得分:28)

您可以使用基于类模板的部分特化的简单类型函数:

#include <type_traits>
#include <tuple>

using namespace std;

template<typename T>
struct remove_first_type
{
};

template<typename T, typename... Ts>
struct remove_first_type<tuple<T, Ts...>>
{
    typedef tuple<Ts...> type;
};

int main()
{
    typedef tuple<int, bool, double> my_tuple;
    typedef remove_first_type<my_tuple>::type my_tuple_wo_first_type;

    static_assert(
        is_same<my_tuple_wo_first_type, tuple<bool, double>>::value, 
        "Error!"
        );
}

此外,这个解决方案可以很容易地推广到删除元组的 i-th 类型:

#include <type_traits>
#include <tuple>

using namespace std;

template<size_t I, typename T>
struct remove_ith_type
{
};

template<typename T, typename... Ts>
struct remove_ith_type<0, tuple<T, Ts...>>
{
    typedef tuple<Ts...> type;
};

template<size_t I, typename T, typename... Ts>
struct remove_ith_type<I, tuple<T, Ts...>>
{
    typedef decltype(
        tuple_cat(
            declval<tuple<T>>(),
            declval<typename remove_ith_type<I - 1, tuple<Ts...>>::type>()
            )
        ) type;
};

int main()
{
    typedef tuple<int, bool, double> my_tuple;
    typedef remove_ith_type<1, my_tuple>::type my_tuple_wo_2nd_type;

    static_assert(
        is_same<my_tuple_wo_2nd_type, tuple<int, double>>::value, 
        "Error!"
        );
}

答案 1 :(得分:8)

我写了一个proposal,它被C ++ 14标准所接受,这对于任何类似“元组”的类型都很容易,即支持tuple_size和{{1 API:

tuple_element

您可以使用几个函数将元组对象转换为新类型:

template<typename T, typename Seq>
    struct tuple_cdr_impl;

template<typename T, std::size_t I0, std::size_t... I>
    struct tuple_cdr_impl<T, std::index_sequence<I0, I...>>
    {
        using type = std::tuple<typename std::tuple_element<I, T>::type...>;
    };

template<typename T>
    struct tuple_cdr
    : tuple_cdr_impl<T, std::make_index_sequence<std::tuple_size<T>::value>>
    { };

这将创建一个整数序列template<typename T, std::size_t I0, std::size_t... I> typename tuple_cdr<typename std::remove_reference<T>::type>::type cdr_impl(T&& t, std::index_sequence<I0, I...>) { return std::make_tuple(std::get<I>(t)...); } template<typename T> typename tuple_cdr<typename std::remove_reference<T>::type>::type cdr(T&& t) { return cdr_impl(std::forward<T>(t), std::make_index_sequence<std::tuple_size<T>::value>{}); } ,其中[0,1,2,...,N)N,然后在tuple_size<T>::value <中为make_tuple(get<I>(t)...)创建一个I的新元组/ p>

测试它:

[1,2,...,N)

我的提案参考实施位于https://gitlab.com/redistd/integer_seq/blob/master/integer_seq.h

答案 2 :(得分:3)

我提出了一个非常类似于@Andy提出的解决方案,但是通过直接使用参数包(使用虚拟包装器)而不是std::tuple来尝试更通用。这样,该操作也可以应用于其他可变参数模板,不仅适用于元组:

#include <type_traits>
#include <tuple>

template <typename... Args> struct pack {};

template <template <typename...> class T, typename Pack>
struct unpack;

template <template <typename...> class T, typename... Args>
struct unpack<T, pack<Args...>>
{
    typedef T<Args...> type;
};

template <typename T, typename Pack>
struct prepend;

template <typename T, typename... Args>
struct prepend<T, pack<Args...>>
{
    typedef pack<T, Args...> type;
};

template <std::size_t N, typename... Args>
struct remove_nth_type;

template <std::size_t N, typename T, typename... Ts>
struct remove_nth_type<N, T, Ts...>
    : prepend<T, typename remove_nth_type<N-1, Ts...>::type>
{};

template <typename T, typename... Ts>
struct remove_nth_type<0, T, Ts...>
{
    typedef pack<Ts...> type;
};

template <typename T, int N>
struct remove_nth;

template <template <typename...> class T, int N, typename... Args>
struct remove_nth<T<Args...>, N>
{
    typedef typename
        unpack<
            T, typename 
            remove_nth_type<N, Args...>::type
        >::type type;
};

template <typename... Args>
struct my_variadic_template
{
};

int main()
{
    typedef std::tuple<int, bool, double> my_tuple;
    typedef remove_nth<my_tuple, 1>::type my_tuple_wo_2nd_type;

    static_assert(
        is_same<my_tuple_wo_2nd_type, tuple<int, double>>::value, 
        "Error!"
        );

    typedef my_variadic_template<int, double> vt;
    typedef remove_nth<vt, 0>::type vt_wo_1st_type;

    static_assert(
        is_same<vt_wo_1st_type, my_variadic_template<double>>::value, 
        "Error!"
        );
}

pack是一个辅助结构,其唯一目的是存储模板参数包。然后可以使用unpack将参数解压缩到任意类模板(thanks to @BenVoigt for this trick)中。 prepend只是将一个类型添加到包中。

remove_nth_type使用部分模板特化来从参数包中删除第n个类型,将结果存储到pack。最后,remove_nth采用任意类模板的特化,从模板参数中删除第n个类型,并返回新的特化。

答案 3 :(得分:1)

这是针对此任务的template元编程的过度设计。它包括通过过滤器tupletemplate的类型进行任意重新排序/重复/删除的功能:

#include <utility>
#include <type_traits>

template<typename... Ts> struct pack {};

template<std::size_t index, typename Pack, typename=void> struct nth_type;

template<typename T0, typename... Ts>
struct nth_type<0, pack<T0, Ts...>, void> { typedef T0 type; };

template<std::size_t index, typename T0, typename... Ts>
struct nth_type<index, pack<T0, Ts...>, typename std::enable_if<(index>0)>::type>:
  nth_type<index-1, pack<Ts...>>
{};

template<std::size_t... s> struct seq {};

template<std::size_t n, std::size_t... s>
struct make_seq:make_seq<n-1, n-1, s...> {};

template<std::size_t... s>
struct make_seq<0,s...> {
  typedef seq<s...> type;
};

template<typename T, typename Pack> struct conc_pack { typedef pack<T> type; };
template<typename T, typename... Ts> struct conc_pack<T, pack<Ts...>> { typedef pack<T, Ts...> type; };

template<std::size_t n, typename Seq> struct append;
template<std::size_t n, std::size_t... s>
struct append<n, seq<s...>> {
  typedef seq<n, s...> type;
};
template<typename S0, typename S1> struct conc;
template<std::size_t... s0, std::size_t... s1>
struct conc<seq<s0...>, seq<s1...>>
{
  typedef seq<s0..., s1...> type;
};

template<typename T, typename=void> struct value_exists:std::false_type {};

template<typename T> struct value_exists<T,
  typename std::enable_if< std::is_same<decltype(T::value),decltype(T::value)>::value >::type
>:std::true_type {};

template<typename T, typename=void> struct result_exists:std::false_type {};
template<typename T> struct result_exists<T,
  typename std::enable_if< std::is_same<typename T::result,typename T::result>::value >::type
>:std::true_type {};

template<template<std::size_t>class filter, typename Seq, typename=void>
struct filter_seq { typedef seq<> type; };

template<template<std::size_t>class filter, std::size_t s0, std::size_t... s>
struct filter_seq<filter, seq<s0, s...>, typename std::enable_if<value_exists<filter<s0>>::value>::type>
: append< filter<s0>::value, typename filter_seq<filter, seq<s...>>::type >
{};

template<template<std::size_t>class filter, std::size_t s0, std::size_t... s>
struct filter_seq<filter, seq<s0, s...>, typename std::enable_if<!value_exists<filter<s0>>::value && result_exists<filter<s0>>::value>::type>
: conc< typename filter<s0>::result, typename filter_seq<filter, seq<s...>>::type >
{};

template<template<std::size_t>class filter, std::size_t s0, std::size_t... s>
struct filter_seq<filter, seq<s0, s...>, typename std::enable_if<!value_exists<filter<s0>>::value && !result_exists<filter<s0>>::value>::type>
: filter_seq<filter, seq<s...>>
{};

template<typename Seq, typename Pack>
struct remap_pack {
  typedef pack<> type;
};

template<std::size_t s0, std::size_t... s, typename Pack>
struct remap_pack< seq<s0, s...>, Pack >
{
  typedef typename conc_pack< typename nth_type<s0, Pack>::type, typename remap_pack< seq<s...>, Pack >::type >::type type;
};

template<typename Pack>
struct get_indexes { typedef seq<> type; };

template<typename... Ts>
struct get_indexes<pack<Ts...>> {
  typedef typename make_seq< sizeof...(Ts) >::type type;
};

template<std::size_t n>
struct filter_zero_out { enum{ value = n }; };

template<>
struct filter_zero_out<0> {};

template<std::size_t n>
struct filter_zero_out_b { typedef seq<n> result; };

template<>
struct filter_zero_out_b<0> { typedef seq<> result; };

#include <iostream>

int main() {
  typedef pack< int, double, char > pack1;
  typedef pack< double, char > pack2;

  typedef filter_seq< filter_zero_out, typename get_indexes<pack1>::type >::type reindex;
  typedef filter_seq< filter_zero_out_b, typename get_indexes<pack1>::type >::type reindex_b;

  typedef typename remap_pack< reindex, pack1 >::type pack2_clone;
  typedef typename remap_pack< reindex_b, pack1 >::type pack2_clone_b;

  std::cout << std::is_same< pack2, pack2_clone >::value << "\n";
  std::cout << std::is_same< pack2, pack2_clone_b >::value << "\n";
}

这里我们有一个类型pack,它包含任意类型的列表。请参阅@LucTouraille关于如何在tuplepack之间移动的简洁答案。

seq包含一系列索引。 remap_pack获取seqpack,并通过抓取原始pack的第n个元素来构建结果pack

filter_seq使用template<size_t>仿函数和seq,并使用仿函数来过滤seq的元素。仿函数可以返回::value类型size_t::result类型seq<...>或两者都不允许,允许一对一或一对多仿函数。 / p>

其他一些帮助函数,例如concappendconc_packget_indexesmake_seqnth_type可以解决问题。< / p>

我使用filter_zero_out进行了测试,::value是基于filter_zero_out_b的过滤器,用于删除0,::result是基于{{1}}的过滤器,也会删除0。

答案 4 :(得分:1)

除了疯狂的TMP东西外,还有一种非常简单的方法来使用C ++ 17 STL函数std::apply

#include <string>
#include <tuple>

template <class T, class... Args>
auto tail(const std::tuple<T, Args...>& t)
{
    return std::apply(
        [](const T&, const Args&... args)
        {
            return std::make_tuple(args...);
        }, t);
}

int main()
{
    // you can use it either like:
    std::tuple<int, double, std::string> t{1, 2., "3"};
    auto _2_3 = tail(t);
    // or that way:
    using tuple_t = decltype(tail(std::tuple<int, double, std::string>{}));
    static_assert(std::is_same_v<std::tuple<double, std::string>, tuple_t>);
}

DEMO