27

This seems to be a very simple question: How does one remove the first (the n-th) type in a std::tuple?

Example:

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

The operation described above would transform tuple1 into tuple2. Is it possible?

cschwan
  • 3,283
  • 3
  • 22
  • 32

5 Answers5

30

You can use a simple type function based on partial specialization of a class template:

#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!"
        );
}

Also, this solution can be easily generalized to remove the i-th type of a tuple:

#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!"
        );
}
Andy Prowl
  • 124,023
  • 23
  • 387
  • 451
8

I wrote a proposal which was accepted into the C++14 standard making it quite easy to do for any "tuple-like" type, i.e. one that supports the tuple_size and tuple_element API:

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>>
    { };

And you can transform a tuple object into the new type with only a couple of functions:

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>{});
}

This creates an integer sequence [0,1,2,...,N) where N is tuple_size<T>::value, then creates a new tuple with make_tuple(get<I>(t)...) for I in [1,2,...,N)

Testing it:

using tuple1 = std::tuple<int, short, double>;
using tuple2 = std::tuple<short, double>;
using transformed = decltype(cdr(std::declval<tuple1>()));
static_assert(std::is_same<transformed, tuple2>::value, "");
static_assert(std::is_same<tuple_cdr<tuple1>::type, tuple2>::value, "");


#include <iostream>

int main()
{
    auto t = cdr(std::make_tuple(nullptr, "hello", "world"));
    std::cout << std::get<0>(t) << ", " << std::get<1>(t) << '\n';
}

My reference implementation for the proposal is at https://gitlab.com/redistd/integer_seq/blob/master/integer_seq.h

daminetreg
  • 9,724
  • 1
  • 23
  • 15
Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
  • I like that proposal and already found myself in the need for that feature several times. So far I've hand-coded it, but a standard implementation would be nice to have. Hope it gets approved. – Andy Prowl Feb 13 '13 at 13:19
  • Thanks, I'll be championing it at the next committee meeting so any comments on the details would be appreciated (I'm unsure if the generic template is overkill and only `int_seq` is needed, and I think I'm going to propose `apply()` should be added to). My email address is easy to find from the GCC mailing lists. – Jonathan Wakely Feb 13 '13 at 13:23
  • 1
    Nice proposal. I'd probably add a hint that you can actually generate such a list in `O(log N)` complexity, though. See [here](http://stackoverflow.com/a/13073076/500104). Also that compiler writers may want to use their built-in functionality (IIRC, GCC and Clang both have something akin to `__builtin_make_indices`). – Xeo Feb 13 '13 at 13:30
  • I only had a quick look at the implementation, so I apologize in advance if your proposal covers this already: one thing I would like to have is the possibility of creating generic ranges, i.e. not just `0..N` but also `M..N`. In [this self-answered Q&A](http://stackoverflow.com/questions/14261183/how-to-make-generic-computations-over-heterogeneous-argument-packs-of-a-variadic) I show a possible implementation for it (see the `index_range` template alias in the "IMPLEMENTATION" section of the answer). Being able to compute the union or intersection of these ranges would be nice as well I guess – Andy Prowl Feb 13 '13 at 13:37
  • @AndyProwl, right, that's definitely useful. The `integer_sequence` class template can hold any sequence, the hard part is how to populate it :) To limit the scope of my proposal I'm only suggesting the library provide a method for creating 0..N but I do have a set of utilities for manipulating sequences. For now I just want to provide the essential foundation, we can build from there. – Jonathan Wakely Feb 13 '13 at 13:56
  • @Xeo, I haven't looked at libc++ or other libs, only libstdc++, but it's not a compiler builtin just a library template. I suspect every impl has something like this - that's the point of standardising it. As I say http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3493.html#changes implementations might want to replace their own existing, incompatible implementations with a standardised one. How they choose to implement the "_see below_" part is unspecified, and could continue to use their existing approach. Thanks for the O(log N) version, I'll include that in my revision of the paper. – Jonathan Wakely Feb 13 '13 at 14:02
3

I came up with a solution very similar to that proposed by @Andy, but that tries to be a bit more generic by working directly on the parameter pack (using a dummy wrapper) rather than on std::tuple. This way, the operation can be applied to other variadic templates as well, not only to tuples:

#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 is an helper structure whose sole purpose is to store a template parameter pack. unpack can then be used to unpack the parameters into an arbitrary class template (thanks to @BenVoigt for this trick). prepend simply prepends a type to a pack.

remove_nth_type uses partial template specialization to remove the nth type from a parameter pack, storing the result into a pack. Finally, remove_nth takes a specialization of an arbitrary class template, remove the nth type from its template parameters, and return the new specialization.

Community
  • 1
  • 1
Luc Touraille
  • 79,925
  • 15
  • 92
  • 137
3

Beside that crazy TMP stuff, there is a very easy way using the C++17 STL function 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);
}
template <class T>
using tail_t = decltype(tail(T{}));
int main()
{
    std::tuple<int, double, std::string> t{1, 2., "3"};
    auto _2_3 = tail(t);
    using tuple_t = tail_t<std::tuple<int, double, std::string>>;
    static_assert(std::is_same_v<std::tuple<double, std::string>, tuple_t>);
}

DEMO.

JulianW
  • 897
  • 9
  • 23
1

This is an over engineered bit of template metaprogramming for this task. It includes the ability to do arbitrary reorders/duplications/removals on the types of a tuple via a filter template:

#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";
}

Here we have a type pack that holds an arbitrary list of types. See @LucTouraille 's neat answer for how to move between tuple and pack.

seq holds a sequence of indexes. remap_pack takes a seq and a pack, and builds a resulting pack by grabbing the nth element of the original pack.

filter_seq takes a template<size_t> functor and a seq, and uses the functor to filter the elements of the seq. The functor can return either a ::value of type size_t or a ::result of type seq<...> or neither, allowing one-to-one or one-to-many functors.

A few other helper functions, like conc, append, conc_pack, get_indexes, make_seq, nth_type round things out.

I tested it with filter_zero_out which is a ::value based filter that removes 0, and filter_zero_out_b which is a ::result based filter that also removes 0.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524