Question

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?

Was it helpful?

Solution

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

OTHER TIPS

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

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.

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.

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

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top