mercredi 29 avril 2020

Is there a way to transparently use move or copy constructor in a template?

I am trying to implement a generic thread safe queue in a way that can work both with move-only objects or copy-only objects. Here is what I tried (I removed all the irrelevant code (locks) for the sake of simplicity):

struct MoveOnly
{
  MoveOnly() = default;
  MoveOnly(const MoveOnly& a) = delete;
  MoveOnly& operator=(const MoveOnly& a) = delete;
  MoveOnly(MoveOnly&& a) = default;
  MoveOnly& operator=(MoveOnly&& a) = default;

  std::vector<int> v;

};

struct CopyOnly
{
  CopyOnly() = default;
  CopyOnly(const CopyOnly &a) = default;
  CopyOnly &operator=(const CopyOnly &a) = default;
  CopyOnly(CopyOnly &&a) = delete;
  CopyOnly &operator=(CopyOnly &&a) = delete;

  std::vector<int> v;
};

template <typename T>
class Queue
{
  std::queue<T> q;

public:
  T pop()
  {
    T t = q.front();
    return t;
  }

  void push(T&& t)
  {
    q.push(std::forward<T>(t));
  }
};

int main()
{
  Queue<MoveOnly> qm;
  qm.push(MoveOnly());
  MoveOnly mo = qm.pop();

  Queue<CopyOnly> qc;
  CopyOnly c;
  qc.push(c);
  CopyOnly&& co = qc.pop();
}

There are several compile errors due to pop: T t = q.front() cannot work with move semantics as the function returns a lvalue reference. T t = std::move(q.front()) would not work with an explicitly deleted move constructor as operator overloading will resolve to the deleted constructor. The same kind of problem used to appear in the push function but it is resolved with perfect forwarding.

Another problem is apparently that return t binds to the move constructor for CopyOnly, and I have difficulties to understand why it does so.

Is there a way to make pop work with both MoveOnlyand CopyOnly objects?


Side question: does it make sense to define an object like CopyOnlywith explicitly deleted move constructors? In which case would it be useful to do that? Because it works if the constructors are implicitly deleted.

Aucun commentaire:

Enregistrer un commentaire