Конструкция агрегата кортежа, которая определяет типы и исключает вызовы конструктора перемещения / копирования? - PullRequest
6 голосов
/ 08 июня 2011

Рассмотрим следующий класс mypair (я не уверен, что это лучший способ сделать что-то, но, похоже, он работает):

#include <iostream>

struct A
{
  A() {}
  A(const A&) { std::cout << "Copy" << std::endl; }
  A(A&&) { std::cout << "Move" << std::endl; }
  std::string s;
};

template <class T0, class T1>
struct mypair
{
  T0 x0;
  T1 x1;
};

template <class T0, class T1, int N = -1>
struct get_class {};

template<class T0, class T1>
struct get_class<T0, T1, 0>
{
  static T0& get_func(mypair<T0, T1>& x) { return x.x0; }
  static const T0& get_func(const mypair<T0, T1>& x) { return x.x0; }
  static T0&& get_func(mypair<T0, T1>&& x) { return std::move(x.x0); }
};

template<class T0, class T1>
struct get_class<T0, T1, 1>
{
  static T1& get_func(mypair<T0, T1>& x) { return x.x1; }
  static const T1& get_func(const mypair<T0, T1>& x) { return x.x1; }
  static T1&& get_func(mypair<T0, T1>&& x) { return std::move(x.x1); }
};

template <int N, class T0, class T1>
auto get(mypair<T0, T1>& x) -> decltype(get_class<T0,T1,N>::get_func(x)) 
{ return get_class<T0,T1,N>::get_func(x); }

#define MAKE_PAIR(x1, x2) mypair<decltype(x1), decltype(x2)>{x1, x2}

int main()
{
  auto x = MAKE_PAIR(A(), A());
  get<0>(x);
  get<1>(x);
}

(ideone link)

В ответе , когда агрегатная инициализация действительна в C ++ 11 , говорится, что мы можем исключить копии / перемещения, выполняя агрегатную инициализацию.

Следовательно, мы можем построить mypair s, используя MAKE_PAIR без необходимости выполнения каких-либо действий или копий.

Я хотел бы обобщить MAKE_PAIR до MAKE_TUPLE, то есть принять любое количество аргументов.

Требования (как с MAKE_PAIR):

(1) Типы выводятся.
(2) Ходы / копии исключаются при построении из временных (то есть строительство происходит на месте).

Существующие библиотечные решения (например, Boost) будут хорошими, хотя я бы предпочел что-то, что принимает ссылки на rvalue. Или просто код здесь тоже отлично, или смесь двух.

Если это вообще возможно, мне бы хотелось, чтобы он оптимизировал пустые элементы, в то время как все еще не допускаются перемещения / вызовы конструктора копирования, но у меня такое чувство, что он требует слишком много.

Ответы [ 2 ]

1 голос
/ 10 октября 2011

make_tuple объявлено make_tuple(Args&&...), поэтому make_tuple(A{foo},B{blah}) может и (в зависимости от соглашений о вызовах вашего компилятора) почти наверняка пропустит ходы, если вы передадите ему временные аргументы в конструкции, как указано выше.

Вы не можете обобщить make_tuple, чтобы заставить конструкцию на месте. Вот контрпример

struct A {
   A (int);
};

struct B {
   B (int, int);
};

Если бы я попытался написать my_make_tuple, который бы построил tuple<A,B> путем построения на месте, то пришлось бы выяснить, что my_make_tuple(1,2,3) эквивалентно по значению make_tupe(A{1},B{2,3})

Должно быть сразу очевидно, что my_make_tuple(1,2,3) должен быть эквивалентен make_tupe(B{1,2},A{3})

Принимая во внимание, что конструкторы A и B могут быть неоднозначно перегружены, и, учитывая, что C ++ не имеет отражения, для меня очевидно, что то, о чем вы просите, невозможно в общем случае.

make_tuple работает только потому, что типы ввода четко разделены в списке аргументов. Не стоит беспокоиться о стоимости переезда операторов, во всех разумных случаях они невероятно дешевы. Кроме того, они могут быть исключены.

Если производительность критична, встроите конструкторы и дайте компилятору понять это.

1 голос
/ 08 июня 2011

В стандартной библиотеке C ++ 0x уже есть такая функция - a std::make_tuple. Даже если вам скучно, вы можете сделать свое собственное.

template<typename... T> std::tuple<T...> make_tuple(T...&& refs) {
    return std::tuple<T...> { std::forward<T>(refs)... };
}
...