Работает ли копирование с структурированными привязками

Применяется ли принудительное копирование для декомпозиции посредством структурированных привязок? Какое из следующих случаев относится к?

// one
auto [one, two] = std::array<SomeClass>{SomeClass{1}, SomeClass{2}};

// two
auto [one, two] = std::make_tuple(SomeClass{1}, SomeClass{2});

// three
struct Something { SomeClass one, two; };
auto [one, two] = Something{};    

Я подозреваю, что только третий случай разрешает копирование, так как первые два будут "разложены" через std::get<> и std::tuple_size<> и std::get<> возвращают значения x, когда аргументы равны rvalues ​​

Цитата из стандарта тоже понравилась бы!

Ответ 1

Применяется ли принудительное копирование для декомпозиции посредством структурированных привязок? Какое из следующих случаев относится к?

Да, все они. Точка структурированных привязок - дать вам именованные ссылки на деструктурированные элементы типа, к которому вы привязываетесь. Это:

auto [one, two] = expr;

Является просто синтаксическим сахаром для:

auto __tmp = expr;
some_type<0,E>& a = some_getter<0>(__tmp);
some_type<1,E>& b = some_getter<1>(__tmp);

Где some_type и some_getter зависят от типа типа, который мы разрушаем (массив, tuple-like или type со всеми публичными нестатическими элементами данных).

Обязательная копия применяется в строке auto __tmp = expr, ни одна из других строк не включает копии.


Там некоторые путаницы вокруг примера в комментариях, так что позвольте мне подробно остановиться на том, что происходит в:

auto [one, two] = std::make_tuple(Something{}, Something{});

Что расширяется в:

auto __tmp = std::make_tuple(Something{}, Something{}); // note that it is from
// std::make_tuple() itself that we get the two default constructor calls as well
// as the two copies.
using __E = std::remove_reference_t<decltype(__tmp)>; // std::tuple<Something, Something>

Тогда, поскольку __E не тип массива, но tuple-like, мы вводим переменные через неквалифицированный вызов get, который просматривается в соответствующем пространстве имен __E. Инициализатор будет xvalue, и типы будут rvalue references:

std::tuple_element_t<0, __E>&& one = get<0>(std::move(__tmp));
std::tuple_element_t<1, __E>&& two = get<1>(std::move(__tmp));

Обратите внимание, что хотя one и two являются ссылками rvalue в __tmp, decltype(one) и decltype(two) будут и дают Something, а не Something&&.

Ответ 2

Интересный вопрос:

#include <iostream>
#include <array>
#include <tuple>
#include <typeinfo>
using std::cout;
using std::endl;

struct SomeClass
{
    int baz;

    SomeClass(int _b): baz(_b) {
        cout << __PRETTY_FUNCTION__ << " = " << baz << endl;
    }
    SomeClass(SomeClass&&) {
        cout << __PRETTY_FUNCTION__ << endl;
    }
    SomeClass(const SomeClass&) {
        cout << __PRETTY_FUNCTION__ << endl;
    }
};

template<typename T> void tell(T&& a)
{
    cout << "Tell: " << __PRETTY_FUNCTION__ << " = " << a.baz << endl;
}

int main()
{
     // one
     cout << "= 1 =" << endl;
     auto [one, two] = std::array<SomeClass,2>{SomeClass{1}, SomeClass{2}};
     cout << "===" << endl;
     tell(one); tell(two);
     // two
     cout << endl << "= 2 =" << endl;
     auto [one2, two2] = std::make_tuple(SomeClass{1}, SomeClass{2});
     cout << "===" << endl;
     tell(one2); tell(two2);
     // three
     cout << endl << "= 3 =" << endl;
     struct Something { SomeClass one{1}, two{2}; };     
     auto [one3, two3] = Something{}; 
     cout << "===" << endl;
     tell(one3); tell(two3);

    return 0;
}

Производит вывод:

= 1 =
SomeClass::SomeClass(int) = 1
SomeClass::SomeClass(int) = 2
===
Tell: void tell(T&&) [with T = SomeClass&] = 1
Tell: void tell(T&&) [with T = SomeClass&] = 2

= 2 =
SomeClass::SomeClass(int) = 2
SomeClass::SomeClass(int) = 1
SomeClass::SomeClass(SomeClass&&)
SomeClass::SomeClass(SomeClass&&)
===
Tell: void tell(T&&) [with T = SomeClass&] = 0
Tell: void tell(T&&) [with T = SomeClass&] = 4199261

= 3 =
SomeClass::SomeClass(int) = 1
SomeClass::SomeClass(int) = 2
===
Tell: void tell(T&&) [with T = SomeClass&] = 1
Tell: void tell(T&&) [with T = SomeClass&] = 2

Второй случай использует либо копировать, либо перемещать (если доступно) конструктор. Значения не были инициализированы, потому что я намеренно не делал этого в конструкторах.

Существует три протокола привязки

  • привязка к массиву
  • привязка к типу типа типа
  • привязка к публичным элементам данных

Во втором случае (извините, у меня нет доступа к С++ 17 pdf, поэтому cppreference):

Каждый идентификатор становится переменной, тип которой является "ссылкой на std::tuple_element<i, E>::type": lvalue reference, если соответствующий Инициализатор - это значение lvalue, rvalue в противном случае. Инициализатор для i-го идентификатора

  • e.get<i>(), если поиск идентификатора попадает в область поиска по члену класса E, находит, по крайней мере, одно объявление (любого вид)
  • В противном случае get<i>(e), где get просматривается только зависящим от аргумента поиска, игнорируя не-ADL-поиск

Первая и вторая стадии примера - это привязки к типу типа. Но... На втором этапе мы используем для инициализации? Функция шаблона, которая строит кортеж:

 std::make_tuple(SomeClass{1}, SomeClass{2});

который фактически либо скопировал, либо переместил бы значения. Может возникнуть дополнительное копирование, но

 auto t = std::make_tuple(SomeClass{1}, SomeClass{2});
 auto [one2, two2] = t;

будет производить этот вывод:

SomeClass::SomeClass(int) = 2
SomeClass::SomeClass(int) = 1
SomeClass::SomeClass(SomeClass&&)      //make_tuple
SomeClass::SomeClass(SomeClass&&)
SomeClass::SomeClass(const SomeClass&) //assignment 
SomeClass::SomeClass(const SomeClass&)

Хотя правильное структурированное связывание с обезжириванием выглядит следующим образом:

 auto t = std::make_tuple(SomeClass{1}, SomeClass{2});
 auto& one2 = std::get<0>(t);
 auto& two2 = std::get<1>(t);

и выходные совпадения оригиналы:

SomeClass::SomeClass(int) = 2
SomeClass::SomeClass(int) = 1
SomeClass::SomeClass(SomeClass&&)
SomeClass::SomeClass(SomeClass&&)
===

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

 auto t = std::tuple<SomeClass&&, SomeClass&&>(SomeClass{1}, SomeClass{2});
 auto& one2 = std::get<0>(t);
 auto& two2 = std::get<1>(t);

и структурированное связывание

 auto [one2, two2] = std::tuple<SomeClass&&, SomeClass&&>(SomeClass{1}, SomeClass{2});

приведет к копированию.