Consider the code fragment below:
using List=std::vector<Data>;
List generate();
List a = generate();
List b = generate();
a += generate(); // append generate() output to a
a += b; // append b to a
I want to make a perfect forward operator+=
so that a += generate() will move elements from the generate() but a += b will copy elements from b.
My implementation:
template<typename T>
DataList & operator+=(DataList &lhs, T &&rhs)
{
using value_type=typename std::remove_cvref_t<T>;
static_assert(std::is_same_v<value_type,DataList>);
lhs.reserve(lhs.size()+rhs.size());
for(auto &&data : rhs)
{
lhs.push_back(std::forward<Data>(data));
}
return lhs;
}
It seems the forward() always do a move.
Complete code:
#include<vector>
#include<format>
#include<iostream>
#include<utility>
class Data
{
std::string value;
public:
Data(const Data &another) : value(another.value)
{}
Data(const std::string &another) : value(another)
{}
Data(Data &&another) : value(std::move(another.value))
{}
std::string to_string()
{
return value.empty()?"<nil>":value;
}
};
using DataList=std::vector<Data>;
DataList generate(const std::string &heading, size_t size)
{
DataList data;
for(size_t i=0;i<size;i++)
{
data.push_back({std::format("{0}:{1}",heading,i)});
}
return data;
}
template<typename T>
DataList & operator+=(DataList &lhs, T &&rhs)
{
using value_type=typename std::remove_cvref_t<T>;
static_assert(std::is_same_v<value_type,DataList>);
lhs.reserve(lhs.size()+rhs.size());
for(auto &&data : rhs)
{
lhs.push_back(std::forward<Data>(data));
}
return lhs;
}
DataList & append(DataList &lhs,const DataList &rhs)
{
lhs.reserve(lhs.size()+rhs.size());
for(auto &data : rhs)
{
lhs.push_back(data);
}
return lhs;
}
DataList & append(DataList &lhs,DataList &&rhs)
{
lhs.reserve(lhs.size()+rhs.size());
for(auto &data : rhs)
{
lhs.push_back(std::move(data));
}
return lhs;
}
int main()
{
auto list=generate("initial",10);
auto anotherList=generate("another",10);
append(list,anotherList); // will do a copy
std::cout<<"after append:\n";
std::cout<< list.back().to_string() << std::endl;
std::cout<< anotherList.back().to_string() << std::endl;
list += anotherList; // expect to be a copy, but it is moved.
std::cout<<"after operator+=:\n";
std::cout<< list.back().to_string() << std::endl;
std::cout<< anotherList.back().to_string() << std::endl; // will print <nil>
return 0;
}
You can apply views::as_rvalue to rhs to move its elements when it is an rvalue, like:
template<typename T>
requires std::same_as<DataList, std::remove_cvref_t<T>>
DataList& operator+=(DataList& lhs, T&& rhs) {
if constexpr (std::is_lvalue_reference_v<T>)
lhs.append_range(rhs);
else
lhs.append_range(std::views::as_rvalue(rhs));
return lhs;
}