5

I have a class NoCopy that is movable, but not copiable.

I need to make a vector of 3 queues of NoCopy. I can create an empty one, but there is no way to add any element.

I can make a std::vector<NoCopy> or std::queue<NoCopy> and populate them. But not for std::vector<std::queue<NoCopy>>.

MWE:

#include <iostream>
#include <vector>
#include <queue>

class NoCopy{
public:
    NoCopy() = default;
    NoCopy& operator = (const NoCopy&) = delete;
    NoCopy(const NoCopy&) = delete;

    NoCopy(NoCopy&&) = default;
    NoCopy& operator = (NoCopy&&) = default;

};
using QNC = std::queue<NoCopy>;

int main(void) {
    QNC q;
    q.push(std::move(NoCopy()));

    std::vector<NoCopy> ncvec;
    ncvec.emplace_back();

    std::cout << "Queue size " << q.size() << ", vector size: " << ncvec.size() << std::endl;

    std::vector<QNC> qvec;
    //????

    return 0;
}

Any ideas?

Ondřej Navrátil
  • 453
  • 1
  • 4
  • 11
  • You could start with a vector of three empty queues and then populate those or swap them with an existing queue: `std::vector qvec(3); qvec[0].swap(q); qvec[1].push(NoCopy());` – jrok Apr 17 '20 at 10:59
  • 3
    The problem seems to be that vector requires noexcept move semantics, and std::queue does not propagate its own move semantics' noexcept-ness from the underlying value – Sam Varshavchik Apr 17 '20 at 11:00
  • @SamVarshavchik [Ditto.](https://coliru.stacked-crooked.com/a/04dc855fe6479bd2) Is [this workaround](https://coliru.stacked-crooked.com/a/b064256725d3b4ea) legal? – jrok Apr 17 '20 at 11:12
  • You can use ncvec.emplace_back(q), emplace_back() uses move semantics. – dheeraj Vadlani Apr 17 '20 at 11:26
  • @SamVarshavchik: It actually does propagate that, but that’s not good enough; I wrote an answer. – Davis Herring Apr 17 '20 at 13:37
  • @jrok: No, you mustn’t specialize type traits (except `std::common_type`). – Davis Herring Apr 17 '20 at 20:18

1 Answers1

4

By default,std::queue is based on std::deque, which is not guaranteed to be nothrow-movable. Neither is the other suitable standard container, std::list; these rules allow implementations that always allocate at least one node/block. std::vector uses copies to reallocate when moving might throw (so as to guarantee exception safety) unless the type is not copyable at all, and those two containers also do not propagate non-copyability from their element type but just fail if you try. The last could be said to be a defect in the standard, since expectations for such propagation keep going up, but fixing it is incompatible with support for incomplete types:

struct Node {
  std::vector<Node> children;
  // …
};

Note that both libstdc++ and libc++ do make those two containers nothrow-movable (as of version 9 in each case), which is a permitted extension, but MSVC’s STL does not.

You can still use std::vector<QNC> v(3);; the constructor “knows” that reallocation is never necessary. Or you can provide a wrapper (e.g., a class derived from std::queue) that is non-copyable or is nothrow-movable; the former will waive the exception safety of std::vector, while the latter will call std::terminate if moving the underlying container does throw.

Davis Herring
  • 36,443
  • 4
  • 48
  • 76
  • Hats down for a rigorous analysis, thank you very much! Eventually, `std::vector v(3);` worked for me, I hope that i will never have to resize the vector, else i will have to wrap it. One reason why I haven't figured this out that is worth mentioning: I had `std::vector qvec` as a member variable of another class, and attempted to default initialize it in the header file. This kind of initialization only allows `{x};`or `=x;` initialization, and I tend to avoid using braces `std::vector qvec {3};` if result is call to a non initializer-list call. – Ondřej Navrátil Apr 18 '20 at 09:41
  • @OndřejNavrátil: Using list-initialization carefully makes sense. It can be a bit verbose, but you can always write `T mem=T(3);` in a class; since C++17, that doesn’t even require movability. – Davis Herring Apr 18 '20 at 14:49