2

I am experimenting with some tuples, and I find myself in the weird position of asking this: how can I copy two tuples that differ in their sizes? Of course, this is intended limited to the minimum length of the two tuples.

So, for instance, let's create three tuples:

std::tuple<int, char, float> a(-1, 'A', 3.14);

std::tuple<int, char, double> b = a;

std::tuple<long, int, double, char> c;

Now, a and b differ in types, and the assignment work (obviously). As for a and c the things get a little more confusing.

My first implementation failed, since I don't know how to recurse on variadic templates with a specific type, so something like this won't work:

template <class T, class U>
void cp(std::tuple<T> from, std::tuple<U> to)
{

}

template <class T, class... ArgsFrom, class U, class... ArgsTo>
void cp(std::tuple<T, ArgsFrom...> from, std::tuple<U, ArgsTo...> to)
{
    std::get<0>(to) = std::get<0>(from);
    // And how to generate the rest of the tuples? 
}

That function won't do anything. So I've devised a second failing attempt, using not the types, but the sizes:

template<class From, class To, std::size_t i>
void copy_tuple_implementation(From &from, To &to)
{
    std::get<i>(to) = std::get<i>(from);

    copy_tuple_implementation<From, To, i - 1>(from, to);
}

template<>
void copy_tuple_implementation<class From, class To, 0>(From &from, To &to)
{
}

template<class From, class To>
void copy_tuple(From &from, To &to)
{
    constexpr std::size_t from_len = std::tuple_size<From>::value;
    constexpr std::size_t to_len   = std::tuple_size<To>::value;

    copy_tuple_implementation<From, To, from_len < to_len ? from_len - 1 : to_len - 1>(from, to);
}

But that won't compile. I have too many errors to display here, but the most significant ones are:

Static_assert failed "tuple_element index out of range"
No type named 'type' in 'std::__1::tuple_element<18446744073709551612, std::__1::__tuple_types<> >'
Read-only variable is not assignable
No viable conversion from 'const base' (aka 'const __tuple_impl<typename __make_tuple_indices<sizeof...(_Tp)>::type, int, int, double>') to 'const __tuple_leaf<18446744073709551615UL, type>'

The interesting part is the index out of range, and the fact that I cannot copy an element with std::get<>.

Can anyone help me in this?

Thanks!

senseiwa
  • 2,369
  • 3
  • 24
  • 47

4 Answers4

5

Here's one possibility, using C++14's ready-made integer sequence template (but this is easily reproduced manually if your library doesn't include it):

#include <tuple>
#include <utility>

template <std::size_t ...I, typename T1, typename T2>
void copy_tuple_impl(T1 const & from, T2 & to, std::index_sequence<I...>)
{
    int dummy[] = { (std::get<I>(to) = std::get<I>(from), 0)... };
    static_cast<void>(dummy);
}

template <typename T1, typename T2>
void copy_tuple(T1 const & from, T2 & to)
{
    copy_tuple_impl(
        from, to,
        std::make_index_sequence<std::tuple_size<T1>::value>());
}

Example:

#include <iostream>

int main()
{
    std::tuple<int, char> from { 1, 'x' };
    std::tuple<int, char, bool> to;
    copy_tuple(from, to);
    std::cout << "to<0> = " << std::get<0>(to) << "\n";
}
Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • 1
    You need a `min` in the index sequence generation? (in case dest is smaller than src) – Yakk - Adam Nevraumont Feb 16 '15 at 12:15
  • 1
    @Yakk: Maybe. Depends on what the goal is here. I find the entire "problem" somewhat arbitrary and contrived. – Kerrek SB Feb 16 '15 at 12:25
  • I see this is the way to go. Too bad Xcode's C++14 won't work for me (`No type named 'index_sequence' in namespace 'std'`... damn!) – senseiwa Feb 16 '15 at 14:03
  • 2
    @senseiwa `index_sequence` is easy to write. Google it on this website, looking for a fast one, ok, [here is a `make_index_sequence` that looks good](http://stackoverflow.com/a/20101039/1774667) -- if it doesn't work, there are myriad different ones all over stack overflow – Yakk - Adam Nevraumont Feb 16 '15 at 14:34
  • Thanks @Yakk, I'm using it now. It baffles me that Xcode says "C++14" as language dialect and yet it won't work, though... – senseiwa Feb 16 '15 at 15:19
  • @senseiwa try `c++1y` instead of `c++14`? -- some compilers, pre-14, supported c++14 features as "the next version after c++0x" (aka c++11) with the name `c++1y`. Similarly, pre-c++17 feature implementation tends to be called `c++1z`. – Yakk - Adam Nevraumont Feb 16 '15 at 15:21
  • Well, it seems funny but a picture is worth a thousand words! No C++1y for Xcode, I think :( http://tinypic.com/view.php?pic=2vtbo0i&s=8#.VOIOqEKJndk – senseiwa Feb 16 '15 at 15:39
3

Another option is to use operator overloading to simulate partial-specialization of your function:

template <std::size_t N>
struct size_t_t {};

template<class From, class To, std::size_t i>
void copy_tuple_implementation(From &from, To &to, size_t_t<i>)
{
    std::get<i>(to) = std::get<i>(from);

    copy_tuple_implementation(from, to, size_t_t<i-1>{});
}

template<class From, class To>
void copy_tuple_implementation(From &from, To &to, size_t_t<0>)
{
    std::get<0>(to) = std::get<0>(from);
}

Or you could just use a helper class:

template<class From, class To, std::size_t i>
struct CopyTuple
{
    static void run(From &from, To &to)
    {
        std::get<i>(to) = std::get<i>(from);

        CopyTuple<From,To,i-1>::run(from, to);
    }
};

template<class From, class To>
struct CopyTuple<From,To,0>
{
    static void run(From &from, To &to)
    {
        std::get<0>(to) = std::get<0>(from);
    }
};
TartanLlama
  • 63,752
  • 13
  • 157
  • 193
1

The goal here is to get a clean syntax at point of use.

I define auto_slice which takes a tuple, and auto slices it for the expression.

The intended use is

 auto_slice(lhs)=auto_slice(rhs);

and it just works.

// a helper that is a slightly more conservative `std::decay_t`:
template<class T>
using cleanup_t = std::remove_cv_t< std::remove_reference_t< T > >;

// the workhorse.  It holds a tuple and in an rvalue context
// allows partial assignment from and to:
template<class T,size_t s0=std::tuple_size<cleanup_t<T>>{}>
struct tuple_slicer{
  T&&t;
  // Instead of working directly within operators, the operators
  // call .get() and .assign() to do their work:
  template<class Dest,size_t s1=std::tuple_size<Dest>{}>
  Dest get() && {
    // get a pack of indexes, and use it:
    using indexes=std::make_index_sequence<(s0<s1)?s0:s1>;
    return std::move(*this).template get<Dest>(indexes{});
  }
  template<class Dest,size_t s1=std::tuple_size<Dest>{},size_t...is>
  Dest get(std::index_sequence<is...>) && {
    // We cannot construct a larger tuple from a smaller one
    // as we do not know what to populate the remainder with.
    // We could default construct them, I guess?
    static_assert(s0>=s1,"use auto_slice on target");
    using std::get;
    return Dest{ get<is>(std::forward<T>(t))... };
  }
  // allows implicit conversion from the slicer:
  template<class Dest>
  operator Dest()&&{
    return std::move(*this).template get<Dest>();
  }
  // now we are doing the assignment work.  This function
  // does the pack expansion hack, excuse the strangeness of the
  // code in it:
  template<class Src, size_t...is>
  void assign(std::index_sequence<is...>,tuple_slicer<Src>&&rhs)&&{
    using std::get;
    int _[]={0,(void(
      get<is>(std::forward<T>(t))=get<is>(std::forward<Src>(rhs.t))
    ),0)...};
    (void)_; // remove warnings
  }
  // assign from another slicer:
  template<class Src,size_t s1>
  void operator=(tuple_slicer<Src,s1>&&rhs)&&{
    using indexes=std::make_index_sequence<(s0<s1)?s0:s1>;
    std::move(*this).assign(indexes{},std::move(rhs));
  }
  // assign from a tuple.  Here we pack it up in a slicer, and use the above:
  template<class Src>
  void operator=(Src&& src)&&{
      std::move(*this) = tuple_slicer<Src>{ std::forward<Src>(src) };
  }
};

// this deduces the type of tuple_slicer<?> we need for us:
template<class Tuple>
tuple_slicer<Tuple> auto_slice(Tuple&&t){
  return {std::forward<Tuple>(t)};
}

The slice is only required on whichever side is smaller, but can be done on both sides (for generic code) if required.

It also works at construction. On the right hand side, it should work with std::arrays and pairs and tuples. On the left hand side, it may not work with arrays, due to requirement to construct with {{}}.

live example

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • The above code is compiled against C++1z, but mostly for `_t` aliases. If you need C++11, just replace `std::blah_t>` with `typename std::blah>::type`, or write a `templateusing blah_t=typename std::blah>::type` alias yourself to keep the crud out of already convoluted template code. – Yakk - Adam Nevraumont Feb 16 '15 at 14:53
  • `_t` is C++14. `_v` might end up being C++1z, though it's in library fundamentals TS at the moment. – T.C. Feb 17 '15 at 06:10
0

Here is the recursive solution your were originally trying to figure out:

#include <tuple>

// Limit case
template<std::size_t I = 0, typename ...From, typename ...To>
typename std::enable_if<(I >= sizeof...(From) || I >= sizeof...(To))>::type
copy_tuple(std::tuple<From...> const & from, std::tuple<To...> & to) {}

// Recursive case
template<std::size_t I = 0, typename ...From, typename ...To>
typename std::enable_if<(I < sizeof...(From) && I < sizeof...(To))>::type
copy_tuple(std::tuple<From...> const & from, std::tuple<To...> & to) 
{
    std::get<I>(to) = std::get<I>(from);
    copy_tuple<I + 1>(from,to);

}

You do not need std::index_sequence or similar apparatus, and this solution has two strengths that your accepted one does not:

  • It will compile, and do the right thing, when from is longer than to: the excess trailing elements of from are ignored.
  • It will compile, and do the right thing, when either from or to is an empty tuple: the operation is a no-op.

Prepend it to this example:

#include <iostream>

int main()
{
    std::tuple<int, char> a { 1, 'x' };
    std::tuple<int, char, bool> b;

    // Copy shorter to longer
    copy_tuple(a, b);
    std::cout << "b<0> = " << std::get<0>(b) << "\n";
    std::cout << "b<1> = " << std::get<1>(b) << "\n";
    std::cout << "b<2> = " << std::get<2>(b) << "\n\n";

    // Copy longer to shorter   
    std::get<0>(b) = 2;
    std::get<1>(b) = 'y';
    copy_tuple(b,a);
    std::cout << "a<0> = " << std::get<0>(a) << "\n";
    std::cout << "a<1> = " << std::get<1>(a) << "\n\n";

    // Copy empty to non-empty  
    std::tuple<> empty; 
    copy_tuple(empty,a);
    std::cout << "a<0> = " << std::get<0>(a) << "\n";
    std::cout << "a<1> = " << std::get<1>(a) << "\n\n";

    // Copy non-empty to empty
    copy_tuple(a,empty);
    return 0;
}

(g++ 4.9/clang 3.5, -std=c++11)

Mike Kinghan
  • 55,740
  • 12
  • 153
  • 182