Создать вектор кортежей из двух векторов движением - PullRequest
2 голосов
/ 10 июля 2019

Я хочу создать std::vector из std::tuple (std::vector<std::tuple<Ts...>>) из двух std::vector, переместив данные std::vector s.

Давайте предположим, что у меня есть структура, подобная этой (добавлено std::cout s для демонстрации проблемы).

template<typename T>
struct MyType
{
    constexpr MyType() { std::cout << "default constructor\n"; }
    constexpr MyType(const T& data) : m_data(data)
    {
        std::cout << "data constructor\n";
    }
    constexpr MyType(const MyType& other) : m_data(other.m_data)
    {
        std::cout << "copy constructor\n"; 
    }

    constexpr MyType(MyType&& other) noexcept : m_data(std::move(other.m_data))
    {
        std::cout << "move constructor\n";
    }
    ~MyType() = default;

    constexpr MyType& operator=(const MyType& other)
    {
        std::cout << "copy operator\n";
        m_data = other.m_data;
        return *this;
    }
    constexpr MyType& operator=(MyType&& other) noexcept
    {
        std::cout << "move operator\n";
        m_data = std::move(other.m_data);
        return *this;
    }

private:
    T m_data{};
};

Теперь мы можем определить operator+ для rvalue ссылок std::vector<MyType<T>>:

template<typename LhsT, typename RhsT>
constexpr auto operator+(std::vector<MyType<LhsT>>&& lhs, std::vector<MyType<RhsT>>&& rhs)
{
    if(lhs.size() != rhs.size())
        throw std::runtime_error("");

    std::vector<std::tuple<MyType<LhsT>, MyType<RhsT>>> ret(lhs.size());

    std::cout << "before transform\n";
    std::transform(std::make_move_iterator(lhs.cbegin()),
                   std::make_move_iterator(lhs.cend()),
                   std::make_move_iterator(rhs.cbegin()),
                   ret.begin(),
                   [](auto&& lhs_val, auto&& rhs_val) {
        return std::make_tuple(lhs_val, rhs_val);
    });
    std::cout << "after transform\n";
    return ret;
}

Теперь вот я столкнулся с проблемой. При запуске этого кода

int main()
{
    std::vector<MyType<int>> int_vec(1);
    std::vector<MyType<float>> float_vec(1);

    std::cout << "before move operator+\n";
    auto int_float_tp_vec = std::move(int_vec) + std::move(float_vec);
    std::cout << "after move operator+\n";
}

вывод таков:

default constructor
default constructor
before move operator+
default constructor
default constructor
before transform
copy constructor
copy constructor
move operator
move operator
after transform
after move operator+

Вопрос в том, почему copy constructor называется? Использует ли std::make_tuple неправильный подход? Как я могу сделать это без вызова copy constructor или copy operator?

LIVE DEMO

Ответы [ 3 ]

5 голосов
/ 10 июля 2019

Вы используете константные итераторы и не std::move элементы в кортеже. Оба из которых заставляют копию. Изменить на это:

std::transform(std::make_move_iterator(lhs.begin()),
               std::make_move_iterator(lhs.end()),
               std::make_move_iterator(rhs.begin()),
               ret.begin(),
               [](auto&& lhs_val, auto&& rhs_val) {
    return std::make_tuple(std::move(lhs_val), std::move(rhs_val));
});
5 голосов
/ 10 июля 2019

Вы забыли использовать std::move здесь:

[](auto&& lhs_val, auto&& rhs_val) {
    return std::make_tuple(std::move(lhs_val), std::move(rhs_val));
});

Помните, что все названное является lvalue.

Кроме того, вы должны исправить ваши итераторы:

std::transform(lhs.begin(),
               lhs.end(),
               rhs.begin(),
               ret.begin(),
               [](auto&& lhs_val, auto&& rhs_val) {
    return std::make_tuple(std::move(lhs_val), std::move(rhs_val));
});

Вы не можете сделать итератор перемещения из константного итератора, это аналогично std::move, примененному к типу const. Фактически, мы можем обойтись без итераторов перемещения и просто вызвать нашу лямбду с ссылками lvalue, из которой мы затем можем двигаться.

3 голосов
/ 10 июля 2019

На этот вопрос уже был дан ответ, но я хотел бы отметить, что вам гораздо удастся решить проблему, если вы откажетесь от распечатки и вместо этого будете полагаться на диагностику компилятора.Вы также можете немного упростить код.Итак, в итоге вы получите:

template<typename T>
struct MyType
{
    constexpr MyType();
    constexpr MyType(const T& ) = delete;
    constexpr MyType(const MyType& ) = delete;

    constexpr MyType(MyType&& ) noexcept;
    ~MyType() = default;

};

template<typename LhsT, typename RhsT>
constexpr auto operator+(std::vector<MyType<LhsT>>&& lhs, std::vector<MyType<RhsT>>&& rhs)
{

    std::vector<std::tuple<MyType<LhsT>, MyType<RhsT>>> ret(lhs.size());

    std::transform(std::make_move_iterator(lhs.cbegin()),
                   std::make_move_iterator(lhs.cend()),
                   std::make_move_iterator(rhs.cbegin()),
                   ret.begin(),
                   [](MyType<int>&& lhs_val, MyType<float>&& rhs_val) {   
        return std::make_tuple(std::move(lhs_val), std::move(rhs_val));
    });
    return ret;
}

int main()
{
    std::vector<MyType<int>> int_vec(1);
    std::vector<MyType<float>> float_vec(1);

    auto int_float_tp_vec = std::move(int_vec) + std::move(float_vec);
}

И очень четкое сообщение:

ошибка: ссылка на привязку типа 'MyType &&' к 'const MyType' отбрасывает квалификаторы

Это сразу указывает на неправильное использование константных итераторов.

...