问题
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::vector
s.
Let's assume I have a struct similar to this (added std::cout
s 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::move
the 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