24

Assuming I have struct and std::tuple with same type layout:

struct MyStruct { int i; bool b; double d; }
using MyTuple = std::tuple<int,bool,double>;

Is there any standartized way to cast one to another?

P.S. I know that trivial memory copying can do the trick, but it is alignment and implementation dependent

ildjarn
  • 62,044
  • 9
  • 127
  • 211
Andrei R.
  • 2,374
  • 1
  • 13
  • 27
  • 6
    "*P.S. I know that trivial memory copying can do the trick*" `std::tuple<>` has no guarantees about triviality, so memory copying here would be UB. – ildjarn Jul 25 '16 at 06:59
  • 1
    Possible duplicate of [TMP: how to write template code which converts any struct into a tuple?](http://stackoverflow.com/questions/13889934/tmp-how-to-write-template-code-which-converts-any-struct-into-a-tuple) – mash Jul 25 '16 at 07:01
  • 6
    @ildjarn it actually has no guarantees even about order in which elements are stored internally. Also agressive space optimisations of user-defined classes with padding and empty classes can ruin your day too. – Revolver_Ocelot Jul 25 '16 at 07:02
  • 2
    `memcpy` sounds like a terrible idea here... – Rerito Jul 25 '16 at 08:03
  • 4
    There is a library ['magic_get'](https://github.com/apolukhin/magic_get) that does introspection on structs. – Ilya Popov Jul 28 '16 at 23:35
  • I updated my solution to be valid even if the members are not default-constructible. – alfC Jul 29 '16 at 12:25
  • @IlyaPopov, that library seems pretty amazing, I still don't understand how is it possible that they achieve that level of introspection. (My best guess is that uses some clever macro disguissed in `struct`) – alfC Jul 29 '16 at 12:32
  • @alfC As far as I understand they use the same trick as in Yakk's answer. – Ilya Popov Jul 29 '16 at 21:03
  • Update: that trick obviously can only be used in C++17 mode. In C++14 mode they use something else. If I remember correctly, the author once explained how it works on boost.devel mailing list, but it seems to be down today. – Ilya Popov Jul 29 '16 at 21:09

4 Answers4

22

We can use structured bindings to convert a struct into a tuple with a bit of work.

Struct-to-tuple is very awkward.

template<std::size_t N>
struct to_tuple_t;

template<>
struct to_tuple_t<3> {
  template<class S>
  auto operator()(S&& s)const {
    auto[e0,e1,e2]=std::forward<S>(s);
    return std::make_tuple(e0, e1, e2);
  }
};

Now, write a to_tuple_t for each size you want to support. This gets tedious. Sadly I know of no way to introduce a parameter pack there.

template<std::size_t N, class S>
auto to_tuple(S&& s) {
  return to_tuple_t<N>{}(std::forward<S>(s));
}

I know of no way to calculate the value of N required either. So you'd have to type the 3 in auto t = to_tuple<3>(my_struct); when you call it.

I am not a master of structured bindings. There is probably a && or & or a decltype that would permit perfect forwarding on these lines:

    auto[e0,e1,e2]=std::forward<S>(s);
    return std::make_tuple(e0, e1, e2);

but without a compiler to play with, I'll be conservative and make redundant copies.


Converting a tuple into a struct is easy:

template<class S, std::size_t...Is, class Tup>
S to_struct( std::index_sequence<Is...>, Tup&& tup ) {
  using std::get;
  return {get<Is>(std::forward<Tup>(tup))...};
}
template<class S, class Tup>
S to_struct( Tup&&tup ) {
  using T=std::remove_reference_t<Tup>;

  return to_struct(
    std::make_index_sequence<std::tuple_size<T>{}>{},
    std::forward<Tup>(tup)
  );
}

SFINAE support based off tuple_size might be good for to_struct.

The above code works with all tuple-likes, like std::pair, std::array, and anything you custom-code to support structured bindings (tuple_size and get<I>).


Amusingly,

std::array<int, 3> arr{1,2,3};
auto t = to_tuple<3>(arr);

works and returns a tuple with 3 elements, as to_tuple is based on structured bindings, which work with tuple-likes as input.

to_array is another possibility in this family.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • @rusty I do not like the aliasing violations in that. Why reinterpret cast into a pointer to something that it isn't? UB should have massive payoff before even considering it. – Yakk - Adam Nevraumont Aug 22 '17 at 16:50
  • *"I know of no way to calculate the value of N required either"*, upper limit is `sizeof(T)`, to you can test aggregate initialization up to that limit, something like [that](http://coliru.stacked-crooked.com/a/ed39db7ae090e3b7). – Jarod42 Aug 17 '20 at 07:16
  • @Jarod42 I think yours breaks with enough elements of no unique address. But yes, that is really close. – Yakk - Adam Nevraumont Aug 17 '20 at 14:39
  • [Indeed](http://coliru.stacked-crooked.com/a/1fe77a07f6fad8a5)... – Jarod42 Aug 17 '20 at 14:57
7

Unfortunately there is no automatic way to do that, BUT an alternative is adapt the struct to Boost.Fusion sequence. You do this once and for all for each new class.

#include <boost/fusion/adapted/struct/adapt_struct.hpp>
...
struct MyStruct { int i; bool b; double d; }

BOOST_FUSION_ADAPT_STRUCT(
    MyStruct,
    (int, i)
    (bool, b)
    (double, d)
)

The use MyStruct as if it where a Fusion.Sequence (it fits generically almost everywhere you already use std::tuple<...>, if you make those functions generic.) As a bonus you will not need to copy your data members at all.

If you really need to convert to std::tuple, after "Fusion-adapting" you can do this:

#include <boost/fusion/adapted/std_tuple.hpp>
#include <boost/fusion/algorithm/iteration/for_each.hpp>
#include <boost/fusion/algorithm/transformation/zip.hpp>
...
auto to_tuple(MyStruct const& ms){
   std::tuple<int, bool, double> ret;
   auto z = zip(ret, ms);
   boost::fusion::for_each(z, [](auto& ze){get<0>(ze) = get<1>(ze);});
   // or use boost::fusion::copy
   return ret;
}

The truth is that std::tuple is a half-backed feature. It is like having STD containers and no algorithms. Fortunatelly we have #include <boost/fusion/adapted/std_tuple.hpp> that allows us to do amazing things.

Full code:

By including the std_tuple.hpp header from Boost.Fusion std::tuple is automatically adapted to a Boost.Fusion sequence, thus the following is possible by using Boost.Fusion as a bridge between your struct and std::tuple:

#include <iostream>
#include <string>
#include <tuple>

#include <boost/fusion/adapted/struct/adapt_struct.hpp>
#include <boost/fusion/algorithm/auxiliary/copy.hpp>
#include <boost/fusion/adapted/std_tuple.hpp>

struct foo
{
  std::string a, b, c;
  int d, e, f;
};

BOOST_FUSION_ADAPT_STRUCT(
    foo,
    (std::string, a)
    (std::string, b)
    (std::string, c)
    (int, d)
    (int, e)
    (int, f)
)

template<std::size_t...Is, class Tup>
foo to_foo_aux(std::index_sequence<Is...>, Tup&& tup) {
  using std::get;
  return {get<Is>(std::forward<Tup>(tup))...};
}
template<class Tup>
foo to_foo(Tup&& tup) {
  using T=std::remove_reference_t<Tup>;
  return to_foo_aux(
    std::make_index_sequence<std::tuple_size<T>{}>{},
    std::forward<Tup>(tup)
  );
}

template<std::size_t...Is>
auto to_tuple_aux( std::index_sequence<Is...>, foo const& f ) {
  using boost::fusion::at_c;
  return std::make_tuple(at_c<Is>(f)...);
}
auto to_tuple(foo const& f){
  using T=std::remove_reference_t<foo>;
  return to_tuple_aux(
    std::make_index_sequence<boost::fusion::result_of::size<foo>::type::value>{},
    f
  );    
}

int main(){


    foo f{ "Hello", "World", "!", 1, 2, 3 };

    std::tuple<std::string, std::string, std::string, int, int, int> dest = to_tuple(f);
    // boost::fusion::copy(f, dest); // also valid  but less general than constructor

    std::cout << std::get<0>(dest) << ' ' << std::get<1>(dest) << std::get<2>(dest) << std::endl;
    std::cout << at_c<0>(dest) << ' ' << at_c<1>(dest) << at_c<2>(dest) << std::endl; // same as above

    foo f2 = to_foo(dest);

    std::cout << at_c<0>(f2) << ' ' << at_c<1>(f2) << at_c<2>(f2) << std::endl;
}

I will not recommend reinterpret_cast<std::tuple<...>&>(mystructinstance.i) because that will result in negative votes and it is not portable.

alfC
  • 14,261
  • 4
  • 67
  • 118
  • 5
    "*I __will not__ recommend `reinterpret_cast&>(mystructinstance.i)` because that will result in negative votes and it is not portable.*" You shouldn't recommend it because it's downright _illegal_, not just because you'll get downvoted. ;-] +1 for mentioning Fusion though! – ildjarn Jul 25 '16 at 17:01
  • Not only is it downright illegal, there's no guarantee that it would work across all implementations. Or even some implementations. – Nicol Bolas Jul 25 '16 at 19:06
  • @ildjam I am a bit demagogue today (it is the current trend), I worry more about downvotes than C++ legality. :) – alfC Jul 25 '16 at 20:57
  • @NicolBolas, yes, that is why I said "it is not portable" even if it works in some system. – alfC Jul 25 '16 at 20:57
  • 2
    If you include the boost fusion std_tuple header, it adapts the `std::tuple` so that you can simply call `boost::fusion::copy()` to copy from the adapted structure to `std::tuple`... you don' need to use your custom iteration.. In effect, it promotes `std::tuple` to a fusion sequence, so copy in either direction should be possible (and it's smart enough to handle repeated types..) – Nim Jul 25 '16 at 21:16
  • 2
    I took the liberty of adding the example for copy, I think it makes this answer complete - if you feel it's irrelevant, remove it.. – Nim Jul 25 '16 at 21:21
  • @Nim, yes, thank you. I was sure that there was a `copy` algorithm` but I couldn't find it in the main manual. It was in the auxiliary section http://www.boost.org/doc/libs/1_61_0/libs/fusion/doc/html/fusion/algorithm/auxiliary/functions/copy.html . What I really didn't figure out yet, how to generate the struct from the tuple if the elements are not default constructible, perhaps there is no way. – alfC Jul 25 '16 at 22:29
  • 1
    @Nim, I figured out how to do it even if the member types are not default constructible. `boost::fusion::copy` can be used but it is not necessary. – alfC Jul 29 '16 at 12:24
  • This is a very interesting answer since as I'm also trying to convert structs to tuple choosing which elements should be moved to the resultant tuple. – Maf Oct 11 '22 at 16:32
6

Is there any standartized way to cast one to another?

There is no way to "cast" the one to the other.

The easiest may be to use a std::tie to pack the tuple out into the struct;

struct MyStruct { int i; bool b; double d; };
using MyTuple = std::tuple<int,bool,double>;

auto t = std::make_tuple(42, true, 5.1);
MyStruct s;

std::tie(s.i, s.b, s.d) = t;

Demo.

You can further wrap this up in higher level macros or "generator" (make style) functions, e.g;

std::tuple<int, bool, double> from_struct(MyStruct const& src)
{
  return std::make_tuple(src.i, src.b, src.d);
}

MyStruct to_struct(std::tuple<int, bool, double> const& src)
{
  MyStruct s;
  std::tie(s.i, s.b, s.d) = src;
  return s;
}

I know that trivial memory copying can do the trick, but it is alignment and implementation dependent?

You mention the "trivial memory copy" would work - only for copying the individual members. So basically, a memcpy of the entire structure to the tuple and vice-versa is not going to always behave as you expect (if ever); the memory layout of a tuple is not standardised. If it does work, it is highly dependent on the implementation.

Niall
  • 30,036
  • 10
  • 99
  • 142
  • I am looking for a more general solution. Which at least doesn't require referring to every struct member. – Andrei R. Jul 25 '16 at 07:59
  • All the solutions I've seen over the years that require individual access to members (i.e. referring to each one), do just that (often using macros); examples here include boost serialisation etc. The pain can be eased with a scripted implementation, the script can generate the boilerplate code, you can also include this in the build such that it is always correct and up to date for the structures being compiled. Unfortunately there is no standard conversion mapping these things. – Niall Jul 25 '16 at 08:04
5

Tuple to struct conversion is trivial, but backward I think is impossible at current C++ level in general.

#include <type_traits>
#include <utility>

#include <tuple>

namespace details
{

template< typename result_type, typename ...types, std::size_t ...indices >
result_type
make_struct(std::tuple< types... > t, std::index_sequence< indices... >) // &, &&, const && etc.
{
    return {std::get< indices >(t)...};
}

}

template< typename result_type, typename ...types >
result_type
make_struct(std::tuple< types... > t) // &, &&, const && etc.
{
    return details::make_struct< result_type, types... >(t, std::index_sequence_for< types... >{}); // if there is repeated types, then the change for using std::index_sequence_for is trivial
}

#include <cassert>
#include <cstdlib>

int main()
{
    using S = struct { int a; char b; double c; };
    auto s = make_struct< S >(std::make_tuple(1, '2', 3.0));
    assert(s.a == 1);
    assert(s.b == '2');
    assert(s.c == 3.0);
    return EXIT_SUCCESS;
}

Live example.

Tomilov Anatoliy
  • 15,657
  • 10
  • 64
  • 169