10

If I have a struct like:

struct Thing
{
  int x;
  int y;
  bool a;
  bool b;
}

Then I can create a Thing object by doing: Thing t {1,2,true,false};. However, if I have a tuple then I am doing something like:

std::tuple<int, int, bool, bool> info = std::make_tuple(1,2,true,false);
Thing t { std::get<0>(info), std::get<1>(info).. // and so on

Is there a better way to do this?

6
  • 1
    Do you need to keep Thing an aggregate type? Commented Jul 18, 2016 at 15:40
  • @NathanOliver preferably.
    – Addy
    Commented Jul 18, 2016 at 15:45
  • 1
    Make a constructor that accepts tuples? That's straightforward and shows the user that it's meant to be used like that.
    – lorro
    Commented Jul 18, 2016 at 15:46
  • Also, if you have such an open aggregate without functions, base class(es), etc., that you often use with std::tuple<>, then I'd consider using just that: a std::tuple<>. I ack any comment on that this is building down type safety (which you can rebuild by adding a tag class element to the tuple if necessary), but it's a functional approach that works. Note that you can add named 'getters'.
    – lorro
    Commented Jul 18, 2016 at 16:08
  • 1
    @lorro tuple is not an aggregate, and defining constructor makes class non-aggregate too. OP prefers to keep class aggregate. Commented Jul 18, 2016 at 16:11

6 Answers 6

11

We can create a generic factory function for creating aggregates from tuple-like types (std::tuple, std::pair, std::array, and arbitrary user-defined tuple-like objects in a structured bindings world):

template <class T, class Tuple, size_t... Is>
T construct_from_tuple(Tuple&& tuple, std::index_sequence<Is...> ) {
    return T{std::get<Is>(std::forward<Tuple>(tuple))...};
}

template <class T, class Tuple>
T construct_from_tuple(Tuple&& tuple) {
    return construct_from_tuple<T>(std::forward<Tuple>(tuple),
        std::make_index_sequence<std::tuple_size<std::decay_t<Tuple>>::value>{}
        );
}

which in your case would be used as:

std::tuple<int, int, bool, bool> info = std::make_tuple(1,2,true,false);
Thing t = construct_from_tuple<Thing>(info); // or std::move(info)

This way, Thing can still be an aggregate (don't have to add constructor/assignments), and our solution solves the problem for many, many types.

As an improvement, we could add SFINAE to both overloads to ensure that they're not callable with invalid tuple types.


Pending accepting wording of how decomposition will work, the qualified call to std::get<Is> may need to be changed to an unqualified call to get<Is> which has special lookup rules. For now, this is moot, since it's 2016 and we don't have structured bindings.


Update: In C++17, there is std::make_from_tuple().

4
  • As a side effect you can construct aggregates from pairs and std::array Commented Jul 18, 2016 at 16:20
  • @Revolver_Ocelot And even from other tuple-like types in a post-structured-bindings world.
    – Barry
    Commented Jul 18, 2016 at 16:20
  • sadly no: you cannot overload std::get (it would be UB) and ADL will not find it as you are explicitely providing template argument. Structured binding have a special rule for lookup of this function. Commented Jul 18, 2016 at 16:22
  • @Revolver_Ocelot Unqualified call to get<i> is explicitly specified as a template-id in the wording paper, so should work. Regardless, I'll leave it as qualified since it's still 2016.
    – Barry
    Commented Jul 18, 2016 at 16:27
6

If you are using c++14 you could make use of std::index_sequence creating helper function and struct as follows:

#include <tuple>
#include <utility>

struct Thing
{
  int x;
  int y;
  bool a;
  bool b;
};

template <class Thi, class Tup, class I = std::make_index_sequence<std::tuple_size<Tup>::value>>
struct Creator;

template <class Thi, class Tup, size_t... Is>
struct Creator<Thi, Tup, std::index_sequence<Is...> > {
   static Thi create(const Tup &t) {
      return {std::get<Is>(t)...};
   }
};

template <class Thi, class Tup>
Thi create(const Tup &t) {
   return Creator<Thi, Tup>::create(t);
}

int main() {
   Thing thi = create<Thing>(std::make_tuple(1,2,true,false));
}

And the version without additional class (with one additional function):

#include <tuple>
#include <utility>

struct Thing
{
  int x;
  int y;
  bool a;
  bool b;
};

template <class Thi, class Tup, size_t... Is>
Thi create_impl(const Tup &t, std::index_sequence<Is...>) {
   return {std::get<Is>(t)...};
}

template <class Thi, class Tup>
Thi create(const Tup &t) {
   return create_impl<Thi, Tup>(t, std::make_index_sequence<std::tuple_size<Tup>::value>{});
}

int main() {
   Thing thi = create<Thing>(std::make_tuple(1,2,true,false));
}

Yet another this time tricky version with just one helper function:

#include <tuple>
#include <utility>

struct Thing
{
  int x;
  int y;
  bool a;
  bool b;
};

template <class R, class T, size_t... Is>
R create(const T &t, std::index_sequence<Is...> = {}) {
   if (std::tuple_size<T>::value == sizeof...(Is)) {
      return {std::get<Is>(t)...};
   }
   return create<R>(t, std::make_index_sequence<std::tuple_size<T>::value>{});
}

int main() {
   Thing thi = create<Thing>(std::make_tuple(1,2,true,false));
}
6
  • 2
    You do not even need classes: coliru.stacked-crooked.com/a/9d5f6743ace68bdb Commented Jul 18, 2016 at 16:10
  • @Barry you think the version with create and create_impl was better?
    – W.F.
    Commented Jul 18, 2016 at 19:55
  • @W.F. Yeah. Now it's two functions each doing one thing, instead of one function doing two things. Hopefully at some point we get the ability to declare a pack in-line so we could do just the one in a way which doesn't involve calling itself. Until then...
    – Barry
    Commented Jul 18, 2016 at 20:13
  • @Barry sure, but I was relying on the compiler which I believe should be able to optimise the code and compile only one branch of the if statement as the condition can be tested at compile time... the one drawback is the additional parameter in the "interface" function or am I wrong?
    – W.F.
    Commented Jul 18, 2016 at 20:18
  • 1
    @W.F. Oh I'm not saying it'll perform worse than the two-function version. I'm saying it's harder for the reader to understand what's going on.
    – Barry
    Commented Jul 18, 2016 at 20:22
3

You may use std::tie:

Thing t;

std::tie(t.x, t.y, t.a, t.b) = info;
1

Here are other ways:

struct Thing
{
    Thing(){}

    Thing(int A_, int B_, int C_, int D_) //1
        : A(A_), B(B_), C(C_), D(D_) {}

    Thing(std::tuple<int,int,bool,bool> tuple) //3
        : A(std::get<0>(tuple)), B(std::get<1>(tuple)),
          C(std::get<2>(tuple)), D(std::get<3>(tuple)) {}

    void tie_from_tuple(std::tuple<int,int,bool,bool> tuple) //4
    {
        std::tie(A,B,C,D) = tuple;
    }

    int A;
    int B;
    bool C;
    bool D;
};

inline Thing tuple_to_thing(const std::tuple<int,int,bool,bool>& tuple) //2
{
    return Thing{std::get<0>(tuple), std::get<1>(tuple),
                 std::get<2>(tuple), std::get<3>(tuple)};
}

int main()
{
    auto info = std::make_tuple(1,2,true,false);

    //1 make a constructor
    Thing one(info);
    //2 make a conversion function
    Thing second = tuple_to_thing(info);
    //3 don't use tuple (just use the struct itself if you have to pass it)
    Thing three{1,2,true,false};
    //4 make member function that uses std::tie
    Thing four;
    four.tie_from_tuple(info);
}
1

Getting fancy with C++17 template argument deduction and using a proxy object (usage example at bottom):

#include <tuple>

using namespace std;

template <class Tuple>
class FromTuple {
    // static constructor, used to unpack argument_pack
    template <class Result, class From, size_t... indices>
    static constexpr Result construct(index_sequence<indices...>, From&& from_tuple) {
        return { get<indices>(forward<
                decltype(from_tuple.arguments)>(from_tuple.arguments))... };
    }

    // used to select static constructor
    using Indices = make_index_sequence<
            tuple_size_v< remove_reference_t<Tuple> >>;

public:
    // construct with actual tuple types only for parameter deduction
    explicit constexpr FromTuple(const Tuple& arguments) : arguments(arguments) {}
    explicit constexpr FromTuple(Tuple&& arguments) : arguments(move(arguments)) {}

    // implicit cast operator delegates to static constructor
    template <class Result>
    constexpr operator Result() { return construct<Result>(Indices{}, *this); }

private:
    Tuple arguments;
};

struct Thing { int x; int y; bool a; bool b; };

int main() {
    std::tuple<int, int, bool, bool> info = std::make_tuple(1,2,true,false);

    Thing thing0((Thing)FromTuple(info));
    Thing thing1{(Thing)FromTuple(info)};

    FromTuple from_info(info);
    Thing thing2(from_info); // only way to avoid implicit cast operator
    Thing thing3{(Thing)from_info};

    return 0;
}

This generalizes to any class or struct, not just Thing. Tuple arguments will be passed into the constructor.

0

Provide an explicit constructor and assignment operator:

struct Thing
{
  int x;
  int y;
  bool a;
  bool b;

  Thing() { }

  Thing( int x, int y, bool a, bool b ): x(x), y(y), a(a), b(b) { }

  Thing( const std::tuple <int, int, bool, bool> & t ) 
  {
    std::tie( x, y, a, b ) = t;
  }

  Thing& operator = ( const std::tuple <int, int, bool, bool> & t ) 
  {
    std::tie( x, y, a, b ) = t;
    return *this;
  }
};

Hope this helps.

1
  • 1
    Now you can't write Thing t {1,2,true,false};
    – Barry
    Commented Jul 18, 2016 at 16:19

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.