Я бы хотел убрать элементы из временного без ненужного перемещения или копирования.
Предположим, у меня есть:
class TP {
T _t1, _t2;
};
Я бы хотел получить _t1
, а _t2
от TP()
. Возможно ли это без копирования / перемещения участников?
Я пробовал с кортежами и пытался «продвинуть» (я не думаю, что это возможно) участников, но лучшее, что я мог получить, - это ход или немедленная смерть участников.
На следующей площадке с использованием B::as_tuple2
члены умирают слишком рано, если результат не привязан к типу без ссылки, тогда члены перемещаются. B::as_tuple
просто перемещается безопасно с auto
на стороне клиента.
Я полагаю, что это должно быть технически возможно, так как временное умирает немедленно, а член умирает, пока они могут быть привязаны к переменным на вызывающем сайте (я ошибаюсь?), А структурированная привязка подобной структуры работает, как задумано.
Можно ли продлить / передать жизнь члена внешней переменной или исключить перемещение / копирование? Мне он нужен с версией C++ 14, но я не мог заставить его работать и на C++ 17, поэтому меня интересуют оба.
#include <tuple>
#include <iostream>
using std::cout;
class Shawty {
/**
* Pronounced shouty.
**/
public:
Shawty() : _id(Shawty::id++) {cout << _id << " ctor\n"; }
Shawty(Shawty && s) : _id(Shawty::id++) { cout << _id << " moved from " << s._id << "\n"; }
Shawty(const Shawty & s) : _id(Shawty::id++) { cout << _id << " copied from " << s._id << "\n"; }
Shawty& operator=(Shawty && s) { cout << _id << " =moved from " << s._id << "\n"; return *this;}
Shawty& operator=(Shawty & s) { cout << _id << " =copied from " << s._id << "\n"; return *this;}
~Shawty() {cout << _id << " dtor\n"; }
int _id;
static int id;
};
int Shawty::id = 0;
class B {
public:
auto as_tuple() && {return std::make_tuple(std::move(_s1), std::move(_s2));}
auto as_tuple2() && {return std::forward_as_tuple(std::move(_s1), std::move(_s2));}
private:
Shawty _s1, _s2;
};
struct S {
Shawty _s1, _s2;
};
int main() {
std::cout << "----------\n";
auto [s1, s2] = B().as_tuple2();
std::cout << "---------\n";
auto tpl1 = B().as_tuple2();
std::cout << "----------\n";
std::tuple<Shawty, Shawty> tpl2 = B().as_tuple2();
std::cout << "----------\n";
std::cout << std::get<0>(tpl1)._id << '\n';
std::cout << std::get<1>(tpl1)._id << '\n';
std::cout << std::get<0>(tpl2)._id << '\n';
std::cout << std::get<1>(tpl2)._id << '\n';
std::cout << s1._id << '\n';
std::cout << s2._id << '\n';
std::cout << "--struct--\n";
auto [s3, s4] = S{};
std::cout << s3._id << '\n';
std::cout << s4._id << '\n';
std::cout << "----------\n";
return 0;
}
Пример со структурированной привязкой используется, чтобы показать, что нечто подобное возможно. Я представляю, как записываю это как кортеж ссылок, чтобы я мог вернуть return tuple<T&&, T&&>{std::move(t1), std::move(t3)};
и позволить t2
умереть. Я бы рассмотрел возможность разрешить такие вещи для всех членов и оставить объект в неопределенном состоянии. Но случай с временными кажется проще, потому что кажется, что исходные члены могут просто привязаться к новым переменным и продлить срок их службы, как в случае с копированием. Как вы думаете, это может быть технически сложно или снижать производительность из-за разметки памяти?
Нет. Невозможно продлить время жизни более чем одного члена сверх времени жизни суперобъекта.
Итак, единственный способ "получить" члены без копирования - это сохранить суперобъект живым и ссылаться на него:
// member function
auto as_tuple3() & {
return std::make_tuple(std::ref(_s1), std::ref(_s2));
}
// usage
B b;
auto [s1, s2] = b.as_tuple3();
Пример продления времени жизни объекта путем привязки ссылки к одному члену. Обратите внимание, что для этого требуется, чтобы член был доступен из того места, где привязана ссылка (не в вашем примере, где член является частным):
auto&& s1 = B{}._s1;
Однако возможно продлить время жизни члена один.
@ Jarod42 Я этого не знал. Не могли бы вы дать ссылку?
Я бы сказал open-std.org/jtc1/sc22/wg21/docs/papers/2017/n4659.pdf § 15.2 стр. 284
@xhamr это конкретное правило, похоже, было добавлено в черновик C++ 20, но, похоже, в основном для разъяснения / лучшей формулировки. Правило the temporary to which the reference is bound or the temporary that is the complete object of a subobject to which the reference is bound persists for the lifetime of the reference
написано на C++ 17.
Почему не std::tie( _s1, _s2 )
? Кажется короче и менее подвержен ошибкам. Также обратите внимание, что вы можете расширить TP
для поддержки структурированной привязки и использовать ее для создания псевдонимов.
@ Yakk-AdamNevraumont std::tie
не позволил бы вам определить их тип, не так ли?
@ user2079303 Почему бы и нет? Буквально замените возвращаемое выражение этим. Вы получаете тот же тип, в 3 раза меньше символов и меньше повторений.
Добавьте поддержку структурированной привязки к вашему типу B
.
class B {
public:
template<std::size_t I, class Self,
std::enable_if_t< std::is_same_v<B, std::decay_t<Self>>, bool> = true
>
friend constexpr decltype(auto) get(Self&& self) {
if constexpr(I==0)
{
using R = decltype(std::forward<Self>(self)._s1)&&;
return (R)std::forward<Self>(self)._s1;
}
else if constexpr(I==1)
{
using R = decltype(std::forward<Self>(self)._s2)&&;
return (R)std::forward<Self>(self)._s2;
}
}
private:
Shawty _s1, _s2;
};
namespace std {
template<>
struct tuple_size<::B>:std::integral_constant<std::size_t, 2> {};
template<std::size_t N>
struct tuple_element<N, ::B>{using type=Shawty;};
}
Код теста:
int main() {
std::cout << "----------\n";
{
auto&& [s1, s2] = B();
}
}
выход:
---------- 0 ctor 1 ctor 1 dtor 0 dtor
Это лучшее, что я могу сделать. Обратите внимание, что s1
и s2
являются ссылками на версию B
с увеличенным сроком службы.
Для справки, это решение реализует интерфейс для привязки структуры. Случай 2, описанный на cppreference, я правильно понимаю?
@ luk32 Да; Я добавил преамбулу, чтобы сказать это, вероятно, с тех пор, как вы обновили страницу.
Предположим,
class TP { T t1, t2, t3; };
, где вам нужны толькоt1
,t3
, как бы вы справились с этим (макет структуры)?