c++containersperfect-forwarding

How to implement perfect forwarding for a container?


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


Solution

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