Create vector of tuples from two vectors by move

爱⌒轻易说出口 提交于 2021-01-27 17:48:42

问题


I want to create a std::vector of std::tuple's (std::vector<std::tuple<Ts...>>) from two std::vector's by moving the data of the std::vectors.

Let's assume I have a struct similar to this (added std::couts to showcase the problem).

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

Now we can define a operator+ for rvalue references of 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;
}

Now here I am facing the problem. When running this code

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

the output is this:

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+

The question is: why is the copy constructor called? Is using std::make_tuple the wrong approach here? How can I do this without calling the copy constructor nor the copy operator?

LIVE DEMO


回答1:


You forgot to use std::move here:

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

Remember, everything named is an lvalue.

In addition, you have to fix your iterators:

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

You cannot make a move-iterator out of const iterator, this is analogous to std::move applied on a const type. In fact, we can do without move-iterators and just get our lambda called with lvalue references, from which we can then move.




回答2:


You use const iterators and you don't std::movethe elements into the tuple. Both of which forces the copy. Change to this:

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



回答3:


The question was already answered, but I'd like to point out that you'd have much better luck of troubleshooting your issue if you ditch the printout, and instead rely on compiler diagnostic. You might also simplify the code a bit. So what you end up with is:

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

And a very clear message:

error: binding reference of type 'MyType&&' to 'const MyType' discards qualifiers

That immediately points out to incorrect usage of const iterators.



来源:https://stackoverflow.com/questions/56977339/create-vector-of-tuples-from-two-vectors-by-move

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!