2

I want to construct a vector using a non-default constructor of a non-copyable, non-movable class. With the default constructor it works fine, and I can construct a vector, as long as I don't resize it. But somehow with a non-default constructor it seems to have to copy. Does anyone know if I can avoid the copy operation during construction with a non-default constructor so I can keep using vector?

#include <vector>
#include <mutex>

class A {
    public:
    A(int num) : vec(num) {}
    private:
    std::vector<std::mutex> vec; 
};

class B {
    public:
    B(int numB, int numA) : vec(numB, numA) {}
    private:
    std::vector<A> vec;
};

int main() {
    B b(2, 3);
    return 0;
}

When I try to compile this I get:

/usr/include/c++/9/bits/stl_uninitialized.h:127:72: error: static assertion failed: result type must be constructible from value type of input range
David
  • 23
  • 4
  • 3
    *"Constructing a non-nested vector works fine"* This is doubtful. Maybe you managed to create a default constructed vector, but you will not be able to insert anything into it. `std::vector` is not compatible with non moveable types. – François Andrieux Jan 14 '22 at 16:12
  • Related: [How can I use something like std::vector?](https://stackoverflow.com/questions/16465633/how-can-i-use-something-like-stdvectorstdmutex) – Drew Dormann Jan 14 '22 at 16:15
  • 1
    You can use a vector that stores a unique_ptr to the non-copyable & non-movable objects. – Eljay Jan 14 '22 at 16:52
  • 1
    @user17732522 Reopened. – François Andrieux Jan 14 '22 at 18:00
  • A nuance to this problem is that when you are calling `vec(numB, numA)`, you are actually converting `numA` to `A(numA)` via `A`'s single argument constructor, which is not marked as `explicit`. – ph3rin Jan 14 '22 at 19:12
  • Here might be a hacky solution that might work: https://godbolt.org/z/Kbz8rnhx6. Don't know if it contains any UB though. – ph3rin Jan 14 '22 at 19:33

1 Answers1

1

You can use std::vector<std::mutex> directly, because the type requirements when using standard library containers are restricted to only those required by the functions called on them.

Once you have constructed a std::vector<std::mutex> with some elements, only the operations that might add new elements or erase old ones require the value type to be movable. Accessing the vector elements and moving the vector itself are not problems.

Constructing the vector with vec(num) works, because it only default constructs a known number of elements. It can constructs the new elements in-place in the storage.

The constructor used by vec(numB, numA) actually takes as arguments the number of elements and a const lvalue reference to an object of the value type. It does not take constructor arguments to construct the new elements from in-place. Instead when you pass it numA, numA is implicitly converted to a A (via the non-explicit constructor) and a reference to that A is passed to the constructor. The constructor is then specified to copy-construct the vector elements from the passed object. But because std::vector<std::mutex> vec; is not copyable, A isn't either and so it fails.

There is however another constructor for std::vector that can construct objects without copy/move constructor: The constructor taking an iterator range. However, to use it we first need to construct an iterator range with the constructor arguments to pass to the vector elements' constructor:

auto args = std::vector(numB, numA);
vec = {args.begin(), args.end()};

Alternatively, with explicit types:

std::vector<int> args(numB, numA);
vec = std::vector<A>(args.begin(), args.end());

If you want to do this in the member-initilizer-list you can delegate this to a member function or lambda and return instead of vec =.

The iterator range constructor construct the vector elements in-place by converting *it and if the iterators are forward iterators (as is the case above), then the constructor does not require any move operations.

Note that vec.assign(args.begin(), args.end()) does not work, since assign is allowed to use assignment instead of construction.

user17732522
  • 53,019
  • 2
  • 56
  • 105