27

I have a tuple of unknown size (it's template parametr of method)

Is it way to get part of it (I need throw away first element of it)

For example, I have tuple<int,int,int>(7,12,42). I want tuple<int,int>(12,42) here

RiaD
  • 46,822
  • 11
  • 79
  • 123
  • 1
    Do you have true variadic template support (`template`)? Also, do you want to copy the values or do you want a *view* of them, as in, references to the original? – Xeo Dec 20 '11 at 01:06
  • @Xeo: I want references better, but it's not really matter. – RiaD Dec 20 '11 at 01:16
  • @Xeo: I don't use variadic templates now. But I use c++0x on g++4.6 and I think they are supported. – RiaD Dec 20 '11 at 01:17
  • @RiaD Yes they are. http://gcc.gnu.org/projects/cxx0x.html Or if you want more C++11 feature get gcc 4.7, it's quite stable right now – Jaffa Dec 20 '11 at 01:38
  • @Geoffroy, I'm not going to use it right now, I just answer Xeo's comment.. – RiaD Dec 20 '11 at 01:39
  • 'twas just as a notice. Read your post and saw the comment :) – Jaffa Dec 20 '11 at 01:43

7 Answers7

16

With help of a compile-time integer list:

#include <cstdlib>

template <size_t... n>
struct ct_integers_list {
    template <size_t m>
    struct push_back
    {
        typedef ct_integers_list<n..., m> type;
    };
};

template <size_t max>
struct ct_iota_1
{
    typedef typename ct_iota_1<max-1>::type::template push_back<max>::type type;
};

template <>
struct ct_iota_1<0>
{
    typedef ct_integers_list<> type;
};

We could construct the tail simply by parameter-pack expansion:

#include <tuple>

template <size_t... indices, typename Tuple>
auto tuple_subset(const Tuple& tpl, ct_integers_list<indices...>)
    -> decltype(std::make_tuple(std::get<indices>(tpl)...))
{
    return std::make_tuple(std::get<indices>(tpl)...);
    // this means:
    //   make_tuple(get<indices[0]>(tpl), get<indices[1]>(tpl), ...)
}

template <typename Head, typename... Tail>
std::tuple<Tail...> tuple_tail(const std::tuple<Head, Tail...>& tpl)
{
    return tuple_subset(tpl, typename ct_iota_1<sizeof...(Tail)>::type());
    // this means:
    //   tuple_subset<1, 2, 3, ..., sizeof...(Tail)-1>(tpl, ..)
}

Usage:

#include <cstdio>

int main()
{
    auto a = std::make_tuple(1, "hello", 7.9);
    auto b = tuple_tail(a);

    const char* s = nullptr;
    double d = 0.0;
    std::tie(s, d) = b;
    printf("%s %g\n", s, d);
    // prints:   hello 7.9

    return 0;
}

(On ideone: http://ideone.com/Tzv7v; the code works in g++ 4.5 to 4.7 and clang++ 3.0)

kennytm
  • 510,854
  • 105
  • 1,084
  • 1,005
12

With C++17, you can use std::apply:

template <typename Head, typename... Tail>
std::tuple<Tail...> tuple_tail(const std::tuple<Head, Tail...>& t)
{
    return std::apply([](auto head, auto... tail) {
        return std::make_tuple(tail...);
    }, t);
}
Jarod42
  • 203,559
  • 14
  • 181
  • 302
9

A tuple slice operation (that also works for std::array and std::pair) can be defined like this (C++14 required):

namespace detail
{
    template <std::size_t Ofst, class Tuple, std::size_t... I>
    constexpr auto slice_impl(Tuple&& t, std::index_sequence<I...>)
    {
        return std::forward_as_tuple(
            std::get<I + Ofst>(std::forward<Tuple>(t))...);
    }
}

template <std::size_t I1, std::size_t I2, class Cont>
constexpr auto tuple_slice(Cont&& t)
{
    static_assert(I2 >= I1, "invalid slice");
    static_assert(std::tuple_size<std::decay_t<Cont>>::value >= I2, 
        "slice index out of bounds");

    return detail::slice_impl<I1>(std::forward<Cont>(t),
        std::make_index_sequence<I2 - I1>{});
}

And an arbitrary subset of a tuple t can be obtained like so :

tuple_slice<I1, I2>(t); 

Where [I1, I2) is the exclusive range of the subset and the return value is a tuple of references to the original tuple. If you want a copying slice operation substitute forward_as_tuple with make_tuple in slice_impl. A thorough elaboration can be found in my blog.

Nikos Athanasiou
  • 29,616
  • 15
  • 87
  • 153
9

There may be an easier way, but this is a start. The "tail" function template returns a copied tuple with all values of the original except the first. This compiles with GCC 4.6.2 in C++0x-mode.

template<size_t I>
struct assign {
  template<class ResultTuple, class SrcTuple>
  static void x(ResultTuple& t, const SrcTuple& tup) {
    std::get<I - 1>(t) = std::get<I>(tup);
    assign<I - 1>::x(t, tup);
  }
};

template<>
struct assign<1> {
  template<class ResultTuple, class SrcTuple>
  static void x(ResultTuple& t, const SrcTuple& tup) {
    std::get<0>(t) = std::get<1>(tup);
  }
};


template<class Tup> struct tail_helper;

template<class Head, class... Tail>
struct tail_helper<std::tuple<Head, Tail...>> {
  typedef typename std::tuple<Tail...> type;
  static type tail(const std::tuple<Head, Tail...>& tup) {
    type t;
    assign<std::tuple_size<type>::value>::x(t, tup);
    return t;
  }
};

template<class Tup>
typename tail_helper<Tup>::type tail(const Tup& tup) {
  return tail_helper<Tup>::tail(tup);
}
Adam Mitz
  • 6,025
  • 1
  • 29
  • 28
  • I justwant to point out that since `template struct tuple_trunc {};` is never instantiated it can be written as: `template struct tuple_trunc;` which will lead to compiler error if someone accidently make this. –  Oct 16 '12 at 11:26
  • @veso - I don't see any "tuple_trunc" in this answer. – Adam Mitz Oct 17 '12 at 22:09
  • Your implementation requires default constructible type though. – Jarod42 Aug 03 '17 at 08:45
6

I made some modifications to Adam's code that would strip off the first N arguments of the tuple, as well as create a new tuple with only the last N types ... Here is the complete code (note: if anyone decides to +1 my answer, also please +1 Adam's answer since that is what this code is based on, and I don't wish to take any credit away from his contribution):

//create a struct that allows us to create a new tupe-type with the first
//N types truncated from the front

template<size_t N, typename Tuple_Type>
struct tuple_trunc {};

template<size_t N, typename Head, typename... Tail>
struct tuple_trunc<N, std::tuple<Head, Tail...>>
{
    typedef typename tuple_trunc<N-1, std::tuple<Tail...>>::type type;
};

template<typename Head, typename... Tail>
struct tuple_trunc<0, std::tuple<Head, Tail...>>
{
    typedef std::tuple<Head, Tail...> type;
};

/*-------Begin Adam's Code-----------

Note the code has been slightly modified ... I didn't see the need for the extra
variadic templates in the "assign" structure.  Hopefully this doesn't break something
I didn't forsee

*/

template<size_t N, size_t I>
struct assign 
{
    template<class ResultTuple, class SrcTuple>
    static void x(ResultTuple& t, const SrcTuple& tup) 
    {
        std::get<I - N>(t) = std::get<I>(tup);  
        assign<N, I - 1>::x(t, tup);  //this offsets the assignment index by N
    }
};

template<size_t N>
struct assign<N, 1> 
{
    template<class ResultTuple, class SrcTuple>
    static void x(ResultTuple& t, const SrcTuple& tup) 
    {
        std::get<0>(t) = std::get<1>(tup);
    }
};


template<size_t TruncSize, class Tup> struct th2;

//modifications to this class change "type" to the new truncated tuple type
//as well as modifying the template arguments to assign

template<size_t TruncSize, class Head, class... Tail>
struct th2<TruncSize, std::tuple<Head, Tail...>> 
{
    typedef typename tuple_trunc<TruncSize, std::tuple<Head, Tail...>>::type type;

    static type tail(const std::tuple<Head, Tail...>& tup) 
    {
        type t;
        assign<TruncSize, std::tuple_size<type>::value>::x(t, tup);
        return t;
    }
};

template<size_t TruncSize, class Tup>
typename th2<TruncSize, Tup>::type tail(const Tup& tup) 
{
    return th2<TruncSize, Tup>::tail(tup);
}

//a small example
int main()
{
    std::tuple<double, double, int, double> test(1, 2, 3, 4);
    tuple_trunc<2, std::tuple<double, double, int, double>>::type c = tail<2>(test);
    return 0;
}
Community
  • 1
  • 1
Jason
  • 31,834
  • 7
  • 59
  • 78
1

Please don't use!

  • This is [most probably] unspecified behaviour. It could stop working at any time.
  • Furthermore, there is a possibility of padding issues (i.e. it may work for int but may fail for your type!).

See comments for discussion. I'm leaving this answer just for reference.


Even simpler:

tuple<int,int,int> origin{7,12,42};
tuple<int, int> &tail1 = (tuple<int, int>&)origin;
tuple<int> &tail2 = (tuple<int>&)origin;
cout << "tail1: {" << get<0>(tail1) << ", " << get<1>(tail1) << "}" << endl;
cout << "tail2: {" << get<0>(tail2) << "}" << endl;

I got:

tail1: {12, 42}
tail2: {42}

I'm not certain that this is not an unspecified behaviour. Works for me: Fedora 20 and

❯ clang --version
clang version 3.3 (tags/RELEASE_33/final)
Target: x86_64-redhat-linux-gnu
Thread model: posix
❯ gcc --version
gcc (GCC) 4.8.2 20131212 (Red Hat 4.8.2-7)
Copyright (C) 2013 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

References: article on voidnish.wordpress.com/.

kgadek
  • 1,446
  • 1
  • 17
  • 18
  • this is a beautiful answer. is it portable? – Don Hatch Oct 23 '14 at 01:51
  • Frankly I have no idea. Looking at my answer now and thinking about that, I suppose that unfortunately it might be not portable. – kgadek Oct 23 '14 at 12:54
  • 1
    This is not portable and is probably undefined behaviour. GCC implements tuples in a particular way that involves the higher index members being stored earlier in the struct. Clang does it the other way around. In general you have no guarantees. – Brian Bi May 17 '15 at 21:53
  • In that time, it worked for me for both `gcc` and `clang` (versions provided in answer). However I would not recommend doing it this way. – kgadek May 18 '15 at 14:58
  • 1
    Though, I do like this and have thought about it as it reduces unnecessary `tuple` copying, binary order is not specified for `tuple` in the `std`, so no, this is not portable. Unless it is specified, YMMV, and may produce unexpected bugs. I would try and put in some `static_assert`s to ensure that your compiler will not mess it up, and/or some function inclusion/exclusion mechanisms to switch to the working one. There also could be some padding issues if you use different types in your tuple, though I'm not sure. – Adrian Oct 20 '17 at 22:20
  • Hmm. Good point about padding issues. Well… in that case I would even further discourage use. I'll update my answer. – kgadek Oct 25 '17 at 19:33
0

The c++17 way:

#include <tuple>
#include <string>

template <size_t __begin, size_t...__indices, typename Tuple>
auto tuple_slice_impl(Tuple &&tuple, std::index_sequence<__indices...>) {
  return std::make_tuple(std::get<__begin + __indices>(std::forward<Tuple>(tuple))...);
}

template <size_t __begin, size_t __count, typename Tuple>
auto tuple_slice(Tuple &&tuple) {
  static_assert(__count > 0, "splicing tuple to 0-length is weird...");
  return tuple_slice_impl<__begin>(std::forward<Tuple>(tuple), std::make_index_sequence<__count>());
}

template <size_t __begin, size_t __count, typename Tuple>
using tuple_slice_t = decltype(tuple_slice<__begin, __count>(Tuple{}));

using test_tuple = std::tuple<int, int, bool, nullptr_t, std::string>;
using sliced_test = tuple_slice_t<2, 2, test_tuple>;

static_assert(std::tuple_size_v<sliced_test> == 2);
static_assert(std::is_same_v<std::decay_t<decltype(std::get<0>(sliced_test{}))>, bool>);
static_assert(std::is_same_v<std::decay_t<decltype(std::get<1>(sliced_test{}))>, nullptr_t>);

#include <cassert>

int main() {
  test_tuple tuple {
    -1, 42, true, nullptr, "hello"
  };
  auto spliced = tuple_slice<3, 2>(tuple);
  assert(std::get<0>(spliced) == nullptr);
  assert(std::get<1>(spliced) == std::get<4>(tuple));
  return 0;
}

Garcia Sylvain
  • 356
  • 4
  • 10