C++ insert class without copy assignment into deque - c++

I'm inserting a class that has copy constructor but does not have copy assignment into a deque, and a simple example is shown below:
class X {
public:
X(const int value): value(value) {}
const int value;
};
void insert() {
std::deque<X> queue;
X x(3);
queue.insert(queue.begin(), x); // this cannot compile
queue.emplace(queue.begin(), x); // this cannot compile
queue.emplace_front(x); // this is OK
}
The compiler complains that the class X does not have a copy assignment due to the const int value in the definition. I understand the part that it does not have a copy assignment, but it indeed has a copy constructor, and I thought to insert into a deque, the element will be copy-constructed, hence I only need a copy constructor and not a copy assignment, where am I wrong here? And how do I deal with this kind of situation where I only have copy constructor and not copy assignment due to const fields?

"I thought to insert into a deque, the element will be copy-constructed, hence I only need a copy constructor and not a copy assignment". The requirements depend on which function you use :
For std::deque<T>::emplace :
Type requirements
-T (the container's element type) must meet the requirements of MoveAssignable, MoveInsertable and EmplaceConstructible.
emplace won't work with X because it can't be move assigned.
For std::deque<T>::emplace_front :
Type requirements
-T (the container's element type) must meet the requirements of EmplaceConstructible.
emplace_front is what you were thinking of and does work with X since it is emplace constructible.
The reason the two functions have different requirements is that elements in a deque are stable under front and back insertion. When you insert an element at the front or back of the deque all pointers and references to its existing elements remain valid, nothing has to move.
With emplace that guarantee isn't provided because it can insert elements anywhere in the container. If you insert in the middle of the container, some number of elements might need to be shifted, so elements need to be assignable to allow this shift. Passing begin() as the iterator doesn't help, the function is implemented to accommodate the case where the iterator may not be begin() or end().

Related

Arithmetic on invalidated iterators

Is behavior of std::distance undefined when called on a pair of std::vector iterators that have been invalidated by moving the vector?
For context:
I'm writing copy and move constructors for a class that has a vector of data and a vector of iterators that point to that data. Once I move the data to its destination, I need to translate the vector of iterators to point to the new container. I would like to avoid creating intermediate index representation in memory.
Is behavior of std::distance undefined when called on a pair of std::vector iterators that have been invalidated by moving the vector?
If the iterators are valid before the move, they will remain valid after the move - so you don't need to recalculate them using std::distance.
(emphasis mine below)
std::vector::vector
After container move construction, references, pointers, and iterators (other than the end iterator) to other remain valid, but refer to elements that are now in *this.
The current standard makes this guarantee via the blanket statement in [container.requirements.general/12], and a more direct guarantee is under consideration via LWG 2321.
[container.requirements.general/12] states that
Unless otherwise specified (either explicitly or by defining a function in terms of other functions), invoking a container member function or passing a container as an argument to a library function shall not invalidate iterators to, or change the values of, objects within that container.
The same blanket statement goes for the move assignment operator and this means that, in accordance with the standard, the iterators will stay valid after the move.
The current wording in LWG 2321 gives a hint of what a new paragraph in the standard could look like if the library working group finalize this - which seems to be hard. LWG 2321 was opened back in 2013.
no move constructor (or move assignment operator when allocator_traits<allocator_type>::propagate_on_container_move_assignment::value is true) of a container (except for array) invalidates any references, pointers, or iterators referring to the elements of the source container. [Note: The end() iterator does not refer to any element, so it may be invalidated. — end note]
If that's too vague, you can use
[container.requirements.general/11.6]
no swap() function invalidates any references, pointers, or iterators referring to the elements of the containers being swapped. [ Note: The end() iterator does not refer to any element, so it may be invalidated. — end note ]
If the iterators are valid before you swap, they are valid after the swap.
Here's an example class using the guarantee given for swap:
#include <vector>
class Foo {
std::vector<int> data{};
std::vector<decltype(data)::iterator> dits{};
public:
Foo() = default;
Foo(const Foo&) = delete; // here, dits would need to be calculated
// A move constructor guaranteed to preserve iterator validity.
Foo(Foo&& rhs) noexcept {
data.swap(rhs.data);
dits.swap(rhs.dits);
}
Foo& operator=(const Foo&) = delete;
// A move assignment operator guaranteed to preserve iterator validity.
Foo& operator=(Foo&& rhs) noexcept {
data.swap(rhs.data);
dits.swap(rhs.dits);
return *this;
}
~Foo() = default;
};

C++ std::vector<std::pair<const int, int>> can not insert element

int main() {
using kv = std::pair<const int ,int>;
std::vector<kv> vec;
kv a{1,1};
vec.insert(vec.begin(),a);
}
I tried to insert element into that vector, but the compiler gives this eerror:
cannot assign to non-static data member 'first' with const-qualified type 'const int'
while push_back() will compile correctly.Why is that? And what's the correct way to insert element into such vector?
ADD:
The reason why I use std::pair<const int, int> is that I want to implement something like std::map, and the key in the key-value pair should not be modified. But I'm not sure if std::pair<const int, int> is the correct approach to do this.
Let's start by observing the following:
std::pair<const int, int> a, b;
a=b;
This will not compile either. Why? Because you are in effect declaring the following class:
class my_pair {
public:
// ... Bunch of irrelevant stuff
const int first;
int second;
};
This class has its default assignment operator deleted. The default assignment operator is equivalent to assigning class members individually:
pair<first,second> &operator=(const pair<first, second> &other)
{
first=other.first;
second=other.second;
return *this;
}
This is a rough example of what a default assignment operator looks like.
But you can't modify the first class member, here, because it is a const class member. Hence, the default assignment operator is deleted. End result: cannot assign a std::pair with a const value to another such pair.
You should now be able to figure out by yourself why insert() on your vector of pairs doesn't work.
insert() is, roughly, equivalent to shifting over all existing values by one, in the vector, after the insertion point, to make room for the new value. In other words:
value[n+1]=values[n];
For all values past the insertion point (I'm ignoring for the moment the work done to grow the vector by one value, which is irrelevant to this discussion).
But the default assignment operator, as I explained, is deleted because the value has a const class member.
push_back works because nothing needs to be inserted, only a new value needs to be added to the end of the vector. If the vector needs to be reallocated, to accomodate its growth, that uses copy or move constructors, which are still valid for a std::pair with a const value.
Due to the const nature of the your data type kv (it has a const member first), it's copy-assignment operator has been implicitly deleted by the compiler.
Specifically, the overload of std::vector::insert you're using requires the data type (kv) be CopyAssignable (can be copied with a form of a = b). Your const member prohibits that.
On the other hand, std::vector::push_back requires the type be CopyInsertable (it can be copy-constructed), which your type is compliant with.
Therefore, insert will not work, but push_back will. The easiest fix is to lose the const from the kv first member. If you don't want someone to modify a key/value, then don't let them. When returning key/value tuples do so as const pointers or references.

Copying vectors without using the element copy assignment operator - portability

If I have a type like this:
class Foo {
public:
Foo();
Foo(const Foo&);
Foo& operator=(const Foo&) = delete;
...
private:
...
};
And I have two vectors of that type:
std::vector<Foo> x;
std::vector<Foo> y;
And I want to copy the contents of x to y, is there a cross-platform way of doing so?
VC++ will do this with y.assign(x.begin(), x.end()), which uses Foo's copy constructor instead of the deleted copy assignment operator. But GCC complains about the missing copy assignment operator, whether you try y = x or y.assign(x.begin(), x.end()).
Is there some way that will work in both?
The standard requires that for y.assign(it1, it2) to work, the element type T be CopyAssignable. That is, copy constructible and assignable. Your type is not assignable, so you can't rely on assign. The same goes for y = x. Section 23.2.3 of the standard describes the requirements for various sequence container operations.
If you have an existing vector y, you can construct a new vector z then swap it with y:
{
std::vector<Foo> z(x.begin(), x.end());
z.swap(y);
}
This uses the range constructor, which requires only that T be EmplaceConstructible, no assignment operator needed! You can then swap the underlying memory without any further copies.
Note that this may result in larger memory usage, since any existing content of y will persist until the freshly swapped z goes out of scope. You could try to mitigate this by first doing
y.clear();
y.shrink_to_fit();
although shrink_to_fit() is only a request, and may not be honoured by your library implementation.
[Live demo]
Prior to C++11 your code would not have been possible: the vector element type had to be CopyAssignable, meaning copy-constructible and assignable. Note that if the type does not meet these requirements, the code is ill-formed with no diagnostic required; this gives compilers latitude to either accept or reject it.
Since C++11, individual operations have their own requirements. The relevant requirements for vector are (source: C++14 Table 100):
emplace_back : MoveInsertible and MoveAssignable
insert: CopyInsertable and CopyAssignable
constructor: EmplaceConstructible
The meanings of these traits are (roughly):
CopyInsertable: has copy-constructor
MoveInsertable: has copy-constructor or move-constructor (or both)
CopyAssignable: has copy-constructor and copy-assignment operator
MoveAssignable: MoveInsertable, and has copy-assignment or move-assignment operator (or both)
EmplaceConstructible: has constructor
The rationale here is that any insertion may cause a memory reallocation, which requires that your objects be able to be either copied or moved. However the Standard does not require the compiler to test whether constructor or assignment operator are each available, and use one if the other isn't. Instead it specifies that both must be available.
You didn't specify whether your class is movable. Assuming it isn't, then that means you cannot use any of the emplace or insert methods, nor any other function that might cause reallocation.
If you are creating y at the time then you can of course use initialization:
vector<int> y = x;
If y already exists, as your question seems to suggest, then you're nearly out of luck: you can't use any insertion functions, so you can only modify the existing elements. And since there is no assignment operator, you can't do this via assignment.
But you can use y.swap() as suggested in Andrew's answer.
Consider adding move-constructor and move-assignment to your class, or redesigning your code so that you do not need to assign to y. (E.g. use a pointer).
If x.size() == y.size() then you can use placement new as a last-resort hack:
for (size_t i = 0; i != y.size(); ++i)
{
y[i].~Foo();
new(&y[i]) Foo(x[i]); // copy-construct
}

Create vector of size elements, without calling copy constructor?

Is there a way to create a vector of N elements without calling the copy constructor and instead calling the elements' default constructor? I do not want a copy constructor for the elements because copying should be prevented.
Here it looks like you can, option 3:
http://en.cppreference.com/w/cpp/container/vector/vector
3) Constructs the container with count default-inserted instances of
T. No copies are made.
but here it looks like you cannot:
http://www.cplusplus.com/reference/vector/vector/vector/
empty container constructor (default constructor) Constructs an empty container, with no elements.
fill constructor Constructs a container with n elements. Each element is a copy of val (if provided).
range constructor Constructs a container with as many elements as the range [first,last), with each element emplace-constructed from its
corresponding element in that range, in the same order.
copy constructor (and copying with allocator) Constructs a container with a copy of each of the elements in x, in the same order.
move constructor (and moving with allocator) Constructs a container that acquires the elements of x. If alloc is specified and
is different from x's allocator, the elements are moved. Otherwise, no
elements are constructed (their ownership is directly transferred). x
is left in an unspecified but valid state.
initializer list constructor Constructs a container with a copy of each of the elements in il, in the same order.
Yes, that is how vector works.
You misread the cplusplus.com wording. It says "fill constructor Constructs a container with n elements. Each element is a copy of val (if provided)"; you can't get away from that. But you don't provide val!
We can trivially demonstrate with the following simple code that copies are not (or, at least, do not need to be) made when the elements are default-constructed:
#include <vector>
#include <iostream>
struct T
{
T() { std::cout << "def-cons\n"; }
~T() { std::cout << "dest\n"; }
T(const T&) = delete;
};
int main()
{
std::vector<T> v(5);
}
(live demo)
The standard itself is clear that the elements need only be default-constructible, no copyability required:
[C++11: 23.3.6.2]: explicit vector(size_type n);
Effects: Constructs a vector with n value-initialized elements.
Requires: T shall be DefaultConstructible.
Complexity: Linear in n.
It depends on the version of C++ you're using. Before C++11, the only
way to get an element into a vector was by copying:
std::vector<T> v( 10 );
was the equivalent of:
std::vector<T> v( 10, T() );
with the default constructed T copied 10 times. C++11 changed this,
and the first form is required to default construct T 10 times,
without any copying.

Const references in std::vector elements

Is it only my compiler or is it forbidden to use cons references in std::vector elements. Consider following structure:
struct Y
{
const int & x;
Y(const int & p_x):
x(p_x)
{
}
};
Now, when I try to push such object onto vector:
std::vector<Y> yv;
int x = 5;
Y y(x);
yv.push_back(y);
I get compiler error: "error: non-static reference member `const int&Y::x', can't use default assignment operator". Shouldn't copy ctor be enough?
The vector elements must be assignable. From section 23.2.4 Class template vector of the C++ standard:
...the stored object shall meet the requirements of Assignable.
You may want to check
std::reference_wrapper
available with C++11
No, because you can't assign to a const reference and the STL containers make use of assignment for moving items around.
You're better using a shared_ptr (boost:: or std:: depending on your environment) or a raw pointer.
An assignment operator is necessary because vector default constructs elements in extra slots ahead of time.
Later, instead of reallocating, it uses the assignment operator to assign those default instances to whatever you push into the vector.
It is generally forbidden to use references in any container. You can justify this by the fact, that a vector for example allocates the data in forehand and references can just be assigned once (during initialization).
I finally used
std::vector< const Type* >
Not the best looking, but it does the same job.