Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Constructor arguments from a tuple

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?

like image 565
Addy Avatar asked Jul 18 '16 15:07

Addy


5 Answers

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().

like image 91
Barry Avatar answered Nov 14 '22 06:11

Barry


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));
}
like image 22
W.F. Avatar answered Nov 14 '22 04:11

W.F.


You may use std::tie:

Thing t;

std::tie(t.x, t.y, t.a, t.b) = info;
like image 20
Jarod42 Avatar answered Nov 14 '22 04:11

Jarod42


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);
}
like image 1
xinaiz Avatar answered Nov 14 '22 06:11

xinaiz


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.

like image 1
Dúthomhas Avatar answered Nov 14 '22 04:11

Dúthomhas