Исключение копирования / перемещения при выводе участников из временного

Я бы хотел убрать элементы из временного без ненужного перемещения или копирования.

Предположим, у меня есть:

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;
}

Предположим, class TP { T t1, t2, t3; };, где вам нужны только t1, t3, как бы вы справились с этим (макет структуры)?

Jarod42 13.09.2018 18:35

Пример со структурированной привязкой используется, чтобы показать, что нечто подобное возможно. Я представляю, как записываю это как кортеж ссылок, чтобы я мог вернуть return tuple<T&&, T&&>{std::move(t1), std::move(t3)}; и позволить t2 умереть. Я бы рассмотрел возможность разрешить такие вещи для всех членов и оставить объект в неопределенном состоянии. Но случай с временными кажется проще, потому что кажется, что исходные члены могут просто привязаться к новым переменным и продлить срок их службы, как в случае с копированием. Как вы думаете, это может быть технически сложно или снижать производительность из-за разметки памяти?

luk32 13.09.2018 21:38
6
2
256
2

Ответы 2

Нет. Невозможно продлить время жизни более чем одного члена сверх времени жизни суперобъекта.

Итак, единственный способ "получить" члены без копирования - это сохранить суперобъект живым и ссылаться на него:

// 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 13.09.2018 18:37

@ Jarod42 Я этого не знал. Не могли бы вы дать ссылку?

eerorika 13.09.2018 18:40

Я бы сказал open-std.org/jtc1/sc22/wg21/docs/papers/2017/n4659.pdf § 15.2 стр. 284

Jarod42 13.09.2018 19:36

@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.

eerorika 13.09.2018 19:37

Почему не std::tie( _s1, _s2 )? Кажется короче и менее подвержен ошибкам. Также обратите внимание, что вы можете расширить TP для поддержки структурированной привязки и использовать ее для создания псевдонимов.

Yakk - Adam Nevraumont 13.09.2018 20:44

@ Yakk-AdamNevraumont std::tie не позволил бы вам определить их тип, не так ли?

eerorika 13.09.2018 21:21

@ user2079303 Почему бы и нет? Буквально замените возвращаемое выражение этим. Вы получаете тот же тип, в 3 раза меньше символов и меньше повторений.

Yakk - Adam Nevraumont 13.09.2018 21:34

Добавьте поддержку структурированной привязки к вашему типу 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 13.09.2018 21:37

@ luk32 Да; Я добавил преамбулу, чтобы сказать это, вероятно, с тех пор, как вы обновили страницу.

Yakk - Adam Nevraumont 13.09.2018 21:42

Другие вопросы по теме