пятница, 11 апреля 2008 г.

Кортежи

У меня появилась идея, как сделать распаковку tuple-ов, тех что в библиотеке boost, более удобной. Обычный синтаксис с использованием get методов
int value = boost::tuples::get<0>(mytuple);
выглядит страшновато, когда нужно часть одного кортежа скопировать в другой, короче разбить один большой кортеж, на несколько маленьких... в общем хочу я странного такого:
using namespace boost::tuples;

tuple<int, std::string> tuple1(44, "aaaaa");
tuple<double, int> tuple2(55.33f, 111);
tuple<int> tuple3(88);
tuple<int, std::string, double, int, int> tuple4(1, "'2'", 3.14159f, 4, 5);

(tuple1, tuple2, tuple3)= tuple4;

int local = 0;

(tuple1, tuple2, tie(local)) = tuple4;
результат должен быть такой, до: tuple4 = (1 '2' 3.14159 4 5) tuple1 = (44 aaaaa) tuple2 = (55.33 111) tuple3 = (88) и после: tuple4 = (1 '2' 3.14159 4 5) tuple1 = (1 '2') tuple2 = (3.14159 4) tuple3 = (5) local = 5 причем это должно работать в обе стороны, так: (tuple1, tuple2, tie(local)) = tuple4; и так: tuple4 = (tuple1, tuple2, tie(local)); это уже похоже на pattern matching в функциональных языках в общем сразу приведу результат =)
#include <boost/tuple/tuple.hpp>
#include <boost/type_traits/add_reference.hpp>


namespace boost { namespace tuples {


  template < class T , class H>
  struct split_;

  template <
      class head, class tail,
      class ex_head, class ex_tail
  >
  struct split_< cons<head, tail>, cons<ex_head, ex_tail> >
  {
      typedef cons<
          head,
          typename split_<
              tail,
              cons<ex_head, ex_tail>
          >::type
      > type;
  };

  template <
      class head,
      class ex_head, class ex_tail
  >
  struct split_< cons<head, null_type>, cons<ex_head, ex_tail> >
  {
      typedef boost::tuples::cons<
          head,
          cons<ex_head, ex_tail>
      > type;
  };




  template <class T>
  struct make_cons_ref_;

  template <class head, class tail>
  struct make_cons_ref_ < cons<head,tail> >
  {
      typedef cons<
          typename add_reference<head>::type,
          typename make_cons_ref_ <tail>::type
      > type;
  };

  template <class head>
  struct make_cons_ref_ < cons<head, null_type > >
  {
      typedef cons<
          typename add_reference<head>::type,
          null_type
      > type;
  };





  template <
      class lhead, class ltail,
      class rhead, class rtail
  >
  typename make_cons_ref_<
      typename split_<
          cons<lhead, ltail>, cons<rhead, rtail>
      >::type
  >::type initialize_tie_ ( cons<lhead, ltail> &tuple_1st, cons<rhead, rtail> &tuple_2nd )
  {
      typedef cons<lhead, ltail>                    _1st;
      typedef cons<rhead, rtail>                    _2nd;
      typedef typename make_cons_ref_<
          typename split_<_1st, _2nd>::type
      >::type                                        _3rd;
      typedef typename make_cons_ref_<
          typename split_<ltail, _2nd>::type
      >::type                                        next_t;

      next_t next = initialize_tie_ (tuple_1st.tail, tuple_2nd);
      _3rd result(tuple_1st.head, next );
      return result;
  }


  template <
      class lhead,
      class rhead, class rtail
  >
  typename make_cons_ref_ <
          cons<lhead,    cons<rhead, rtail> >
  >::type initialize_tie_ ( cons<lhead, null_type> &tuple_1st, cons<rhead, rtail> &tuple_2nd )
  {
      typedef cons<lhead, null_type>
                                                  _1st;
      typedef cons<rhead, rtail>    _2nd;

      typedef typename make_cons_ref_ <
              cons<lhead,
              cons<rhead, rtail>
          >
      >::type                                        _3rd;

      typedef typename make_cons_ref_<
          cons<rhead, rtail>
      >::type                                        next_t;

      next_t next = initialize_tie_ (tuple_2nd);
      _3rd result(tuple_1st.head, next);
      return result;
  }

  template <
      class head, class tail
  >
  typename make_cons_ref_ <
      cons<head, tail>
  >::type initialize_tie_ ( cons<head, tail> &tuple_2nd)
  {
      typedef cons<head, tail>                            _2nd;
      typedef typename make_cons_ref_<_2nd>::type            _3rd;
      typedef typename make_cons_ref_<tail>::type            next_t;

      next_t next = initialize_tie_ ( tuple_2nd.tail );
      _3rd result( tuple_2nd.head, next );
      return result;
  }

  template <
      class head
  >
  typename make_cons_ref_ <
          cons<head, null_type>
  >::type

      initialize_tie_ (  
          cons<head, null_type> &tuple_2nd)
      {
          typedef cons<head, null_type>                    _2nd;
          typedef typename make_cons_ref_<_2nd>::type        _3rd;

          return _3rd( tuple_2nd.head, null_type() );
      }




  //template arg
  template <
      class lhead, class ltail,
      class rhead, class rtail
  >
  //return value
  typename make_cons_ref_ <
      typename split_<
          cons<lhead, ltail>,    cons<rhead, rtail>
      >::type
  >::type
  operator , ( cons<lhead, ltail> &tuple_1st, cons<rhead, rtail> &tuple_2nd )
  {
      return initialize_tie_(tuple_1st, tuple_2nd);
  }

}; };

#include <iostream>
#include <conio.h>
#include <boost/tuple/tuple_io.hpp>

using namespace boost::tuples;


int main()
{
  tuple<int, std::string> tuple1(44, "aaaaa");
  tuple<double, int> tuple2(55.33f, 111);
  tuple<int> tuple3(88);
  tuple<int, std::string, double, int, int> tuple4(1, "'2'", 3.14159f, 4, 5);

  std::cout << "tuple4 = " << tuple4 << std::endl ;
  std::cout << "tuple1 = " << tuple1 << std::endl;
  std::cout << "tuple2 = " << tuple2 << std::endl;
  std::cout << "tuple3 = " << tuple3 << std::endl;

  std::cout << "(tuple1, tuple2, tuple3) = tuple4" << std::endl;

  (tuple1, tuple2, tuple3)= tuple4;

  std::cout << "tuple4 = " << tuple4 << std::endl ;
  std::cout << "tuple1 = " << tuple1 << std::endl;
  std::cout << "tuple2 = " << tuple2 << std::endl;
  std::cout << "tuple3 = " << tuple3 << std::endl;

  std::cout << "(tuple1, tuple2, tie(local)) = tuple4" << std::endl;
  int local = 0;  
  (tuple1, tuple2, tie(local)) = tuple4;

  std::cout << "local = " << local << std::endl;


  _getch();
  return 0;
}
этому коду явно не хватает комментариев)) в двух словах принцип здесь такой кортеж представляет из себя по сути экземпляр списка типов, вот такого вот
template<class H, class T>
struct cons
{
  H head;
  T tail;
};
(реально конечно не так, пример сильно утрирован) head - хранимое значение, tail - может быть либо null_type, либо другой cons. В общем так можно получить последовательность любой длины, вот такая вот последовательность:
cons<int, cons<long, cons<std::string, null_type> > >
соответствует кортежу tuple: int, long, string. boost::tuple так и реализован, с той лишь разницей, что tuple наследует от списка типов, а линеаризация достигается с помощью параметров шаблона, если заглянуть в исходники, то можно увидеть там:
template <class T0, class T1, class T2, class T3, class T4,
          class T5, class T6, class T7, class T8, class T9>

class tuple :
  public detail::map_tuple_to_cons<T0, T1, T2, T3, T4, T5, T6, T7, T8, T9>::type
................
typedef cons<T0,
               typename map_tuple_to_cons<T1, T2, T3, T4, T5,
                                          T6, T7, T8, T9, null_type>::type
              > type;
в общем моя идея состоит в том, что-бы перегрузить оператор ,(comma) для кортежей так, что-бы он возвращал кортеж ссылок на элементы своих аргуметов Для этого сначала нужно вычислить тип результата. Для этого сначала кортежи объединяются метафункцией split_
typedef typename split_<
cons<int, null_type>,
cons<long, cons<double, null_type> >
>::type result;
где result будет равно
cons<int, cons<long, cons<double, null_type> > >
Метафункция make_cons_ref_ - делает параметры ссылками, то-есть после ее применения у нас будет последовательность вида:
cons<int&, cons<long&, cons<double&, null_type> > >
Теперь остается ее проинициализировать. Здесь самая большая сложность со ссылками, так как они не имеют конструктора по умолчанию и должны инициализироваться во время создания. Для этого предназначена функция initialize_tie_. Структура cons имеет конструктор cons(H head, T tail), соответственно функция initialize_tie_ вызывается рекурсивно и всегда возвращает tail. С этим кодом есть одна проблема. Он соответствует стандарту только для случая tuple3 = tuple2, tuple1; то есть для 2х кортежей, так как временный объект не может использоваться как rvalue при инициализации не константной ссылки. Но у компилятора Visual Studio на этот счет свое мнение =), там есть расширение, которое позволяет это делать, в общем в студии это отлично работает. Производительность - такая-же как и при использовании get функций, компилятор отлично справляется с оптимизацией, я проверял =) Может быть мне удастся сделать это более переносимым, посмотрим...

Комментариев нет: