Why std::vector requires operator = - c++

I have a question about a class that we can store in vector.
What is the requirement that can be stored in a vector?
It seems that such class has to have assignment operator. But I am not sure if that's all or not.
Let me give you an example. class A has const int member. If I don't write operator =, it doesn't compile. But in this example, this operator does nothing. This program displays 10 and 20 correctly. It looks that operator = is required but not used in reality.
#include <iostream>
#include <vector>
class A {
public:
A(int a) : a_(a) {}
A& operator =(const A& a2) { return *this;} // Without this, compile fails.
void print() const {
std::cerr << a_ << std::endl;
}
private:
const int a_;
};
int main(int argc, char** argv) {
std::vector<A> v;
v.push_back(A(10));
v.push_back(A(20));
for (const A& a : v) a.print();
}

This might surprise you:
v.push_back(A(20));
v.push_back(A(10));
std::sort(begin(v), end(v));
There are aspects of vector itself that require assignability, though I don't know, offhand, which (and I can't tell by compiling your code, since my compiler doesn't complain when I remove operator=()). According to Wikipedia (which references the relevant portion of the '03 standard), elements must be CopyConstructible and Assignable.
EDIT: Coming back to this a day later, it seems forehead-slappingly obvious when std::vector requires Assignable — any time it has to move elements around. Add a call to v.insert() or v.erase(), for example, and the compile will fail.

If I don't write operator =, it doesn't compile.
That surprised me, so I had a look into the standard and I found:
Your example has an implicitly deleted copy constructor but should still compile if a conforming C++11 standard library is at hand.
The only expression that puts constraints on the type used by the vector in your example is push_back.
The push_back() method of a sequence container type X<T,A> with allocator A and value_type T, requires T to be:
CopyInsertable if an lvalue or const rvalue reference is passed
MoveInsertable if a non-const rvalue is passed
Which means it requires a valid copy constructor or (as in this case) a valid move constructor which will be implicitly present from your code. Therefore the compilation should not fail in any compiler with a valid C++11 standard library.
Operations that require the type, contained in a vector to be assignable:
Ancillary conditions
typdef std::vector<T> X;
X a,b;
X&& rv;
X::value_type t;
X::value_type&& u;
X::size_type n;
X::const_iterator p,q; // p = valid for a, q = valid and dereferencable
initializer_list<T> il;
[i,j) -> valid iterator-range
Pessimistic* list of operations
The operations, which require T to be assignable, if X is a vector, are:
Statement Requirement on T
a = b; CopyInsertable, CopyAssignable
a = rv; MoveInsertable, MoveAssignable
a = il; CopyAssignable
a.emplace(p, args); MoveInsertable, MoveAssignable
a.insert(p, t); CopyAssignable
a.insert(p, u); MoveAssignable
a.insert(p, n, t); CopyInsertable, CopyAssignable
a.insert(p, i, j); EmplaceConstructible[from *i], MoveInsertable, MoveAssignable
a.insert(p, il); -> a.insert(p, il.begin(), il.end());
a.erase(q); MoveAssignable
a.erase(q1,q2) MoveAssignable
a.assign(i,j); Assignable from *i
a.assign(il); -> a.assign(il.begin(), il.end());
a.assign(n,t) CopyAssignable
* = Pessimistic means that there may exist certain conditions for several requirements to actually come into effect. If you use one of the expressions listed above, your type T will likely be required to be assignable.

push_back on vector will make vector grow in memory, that means
old objects needs to be copied to new object via assignment operator=
hence you need assignment operator=.

The availability of copy constructor and assignment operator are guaranteed by the compiler if you don't create any of your own. (the correctness of default implementation, is a different story though). In your example, you almost don't have to overload A::opeartor=().
The problem here is not a "missing" operator=(), but that the default one cannot be used, because you have declared a const data member
const int a_;
The default compiler generated operator=() cannot assign value to a const member. So you have to overload your self:
A & A::opeartor=(const A & in)
{
*const_cast<int*>(&a_) = in.a_; // !!!you are expected to do this.
}
So, your version of A::operator=() that does nothing, although making the code compile, does't change the a_ value of the left hand operand,

Related

Why does std::map::operator[] assignment require an argumentless constructor?

I have the following minimal example reproducing an error in my code:
#include <unordered_map>
#include <iostream>
class B
{
public:
B(int b) : m_b{ b } {}
int m_b;
};
int main()
{
using std::cout, std::endl;
std::unordered_map<int, B> ab{};
ab[1] = B(3);
//ab.insert(std::pair<int, B>(1, B(3)));
cout << ab[1].m_b << endl;
}
This fails with a long and unwieldy error which basically amounts to saying that there is no constructor for B without any arguments. The error stems from ab[1] = B(3) Why is that needed? And why does using insert instead of operator[] not need that constructor?
Bonus points for why this line in my original code:
Vec2 pos{ m_orbits[&p].positionAtTime(m_time + dt) };
also requires a non - parameterized constructor. I could not reproduce that error in my minimal example, but m_orbits is an unordered map with pointers to Particle objects as keys and Orbit objects as values. positionAtTime is a const member function of Orbit that calculates the position of a particle in the orbit at a certain time.
Why is [a constructor for B without any arguments] needed?
This is because std::map::operator[] required the mapped_type (i.e. in your case B) to be default constructable.
Inserts value_type(key, T()) if the key does not exist. This function is equivalent to return insert(std::make_pair(key, T())).first->second;
key_type must meet the requirements of CopyConstructible.
mapped_type must meet the requirements of CopyConstructible and DefaultConstructible.
If an insertion is performed, the mapped value is value-initialized (default-constructed for class types, zero-initialized otherwise) and a reference to it is returned.
When you provide a user defined constructor (i.e. B(int b)), the compiler will not generate a default constructor automatically, and thereby A can not be default construable.
If some user-declared constructors are present, the user may still force the automatic generation of a default constructor by the compiler that would be implicitly-declared otherwise with the keyword default.
Hence, the above error!
why does using insert instead of operator[] not need that constructor?
Because std::map::insert relies on the value_type (i.e. std::pair<const Key, T>). For your ab this is std::pair<const int, B>. From the cppreference.com the function overloads:
1-3) Inserts value. The overload (2) is equivalent to emplace(std::forward<P>(value)) and only participates in overload resolution if std::is_constructible<value_type, P&&>::value == true.
4-6) Inserts value in the position as close as possible, just prior(since C++11), to hint. The overload (5) is equivalent to emplace_hint(hint, std::forward<P>(value)) and only participates in overload resolution if std::is_constructible<value_type, P&&>::value == true.
So as long as B is constructable the std::pair<const int, B> also can be constructed and std::map::insert can be work.

Why does this std::vector::emplace_back fail?

I'm coming across a compiler error that says:
attempting to reference a deleted function
#include <iostream>
#include <vector>
template <typename T>
struct Container
{
Container() = default;
Container(const Container& other) = delete;
Container(T* ptr) : ptr(ptr) {}
T* ptr;
~Container() { delete ptr; }
};
struct Foo { Foo(int a, int b) {} };
int main()
{
std::vector<Container<Foo>> myvector;
myvector.push_back(new Foo(1, 2)); // I understand why this doesn't work.
myvector.emplace_back((new Foo(1, 2))); // I don't understand why this fails
}
I understand why it says attempting to reference a deleted constructor when I do std::vector::push_back(), because this does a copy and needs to call the copy constructor, which I deleted.
But std::vector::emplace_back() is supposed to take the constructor arguments of the type it holds. When I emplace back, I give it a pointer to a Foo, and this should be forwarded to the Container::Container(T* ptr) constructor.
What am I missing?
Declaring a User-Defined copy constructor will not define an implicit move constructor; T must either have a copy constructor or a move constructor to push_back or emplace_back* an object into a std::vector<T>.
From the docs, see the requirements on T to instantiate a std::vector<T>. (No restriction here, read on) ..emphasis mine
The requirements that are imposed on the elements depend on the actual operations performed on the container. Generally, it is required that element type meets the requirements of Erasable, but many member functions impose stricter requirements. This container (but not its members) can be instantiated with an incomplete element type if the allocator satisfies the allocator completeness requirements.
From std::vector<...>::push_back:
Type requirements
T must meet the requirements of CopyInsertable in order to use overload (1).
T must meet the requirements of MoveInsertable in order to use overload (2).
From std::vector<...>::emplace_back:
Type requirements
T (the container's element type) must meet the requirements of MoveInsertable and EmplaceConstructible.
For emplace_back here, your code would fulfill the EmplaceConstructible criteria, however, because reallcations can happen, you must equally fulfill MoveInsertable.

std::move_if_noexcept calls copy-assignment even though move-assignment is noexcept; why?

I'm trying to get as close to the Strong Exception Guarantee as possible, but when playing around with std::move_if_noexcept I ran into some seemingly weird behavior.
Despite the fact that the move-assignment operator in the following class is marked noexcept, the copy-assignment operator is called when invoked with the return value of the function in question.
struct A {
A () { /* ... */ }
A (A const&) { /* ... */ }
A& operator= (A const&) noexcept { log ("copy-assign"); return *this; }
A& operator= (A&&) noexcept { log ("move-assign"); return *this; }
static void log (char const * msg) {
std::cerr << msg << "\n";
}
};
int main () {
A x, y;
x = std::move_if_noexcept (y); // prints "copy-assign"
}
The Question
Why isn't the move-assignment operator called in the previous snippet?
The Introduction
The name of move_if_noexcept certainly implies that the function will yield an rvalue-reference as long as this operation is noexcept, and with this in mind we soon realize two things:
A simple cast from T& to T&& or T const& can never throw an exception, so what is the purpose of such function?
How can move_if_noexcept magically deduce the context in which the returned value will be used?
The answer to realization (2) is equally scary as natural; move_if_noexcept simply can't deduce such context (since it's not a mind-reader), and this in turn means that the function must play by some static set of rules.
The TL;DR
move_if_noexcept will, no matter the context in which it is called, conditionally return an rvalue-reference depending on the exception specification of the argument type's move-constructor, and it was only meant to be used when initializing objects (ie. not when assigning to them).
template<class T>
void intended_usage () {
T first;
T second (std::move_if_noexcept (first));
}
A better name could have been move_if_move_ctor_is_noexcept_or_the_only_option; though a bit tedious to type, at least it would have expressed the intended usage.
The Birth Of move_if_noexcept
Reading the proposal (n3050) that gave birth to std::move_if_noexcept, we find the following paragraph (emphasize mine):
We propose that instead of using std::move(x) in those cases, thus granting permission for the compiler to use any available move constructor, maintainers of these particular operations should use std::move_if_noexcept(x), which grants permission move unless it could throw and the type is copyable.
Unless x is a move-only type, or is known to have a nonthrowing move constructor, the operation would fall back to copying x, just as though x had never acquired a move constructor at all.
So, what is it that move_if_noexcept does?
std::move_if_noexcept will conditionally cast the passed lvalue-reference to an rvalue-reference, unless;
A potential move-constructor might throw, and;
the type is CopyConstructible.
// Standard Draft n4140 : [utility]p2
template<class T>
constexpr conditional_t<
!is_nothrow_move_constructible::value && is_copy_constructible<T>::value,
const T&, T&&
> move_if_noexcept (T& x) noexcept;
This basically means that it will only yield an rvalue-reference if it can prove that it is the only viable alternative, or if it is guaranteed not to throw an exception (expressed through noexcept).
The Verdict
std::move is an unconditional cast to an rvalue-reference, whereas std::move_if_noexcept depends on the ways an object can be move-constructed - therefore it should only be used in places where we are actuallying constructing objects, not when we are assigning to them.
The copy-assignment operator in your snippet is invoked since move_if_noexcept can't find a move-constructor marked noexcept, but since it has a copy-constructor the function will yield a type, A const&, that is suitable for such.
Please note that a copy-constructor qualifies as the type being MoveConstructible, this means that we can make move_if_noexcept return an rvalue-reference through the following adjustment of your snippet:
struct A {
A () { /* ... */ }
A (A const&) noexcept { /* ... */ }
...
};
Examples
struct A {
A ();
A (A const&);
};
A a1;
A a2 (std::move_if_noexcept (a1)); // `A const&` => copy-constructor
struct B {
B ();
B (B const&);
B (B&&) noexcept;
};
B b1;
B b2 (std::move_if_noexcept (b1)); // `B&&` => move-constructor
// ^ it's `noexcept`
struct C {
C ();
C (C&&);
};
C c1;
C c2 (std::move_if_noexcept (c1)); // `C&&` => move-constructor
// ^ the only viable alternative
struct D {
C ();
C (C const&) noexcept;
};
C c1;
C c2 (std::move_if_noexcept (c1)); // C&& => copy-constructor
// ^ can be invoked with `T&&`
Further reading:
cppreference.com - MoveConstructible
cppreference.com - CopyConstructible
cppreference.com - std::move_if_noexcept

Is default constructor elision / assignment elision possible in principle?

. or even allowed by the C++11 standard?
And if so, is there any compiler that actually does it?
Here is an example of what I mean:
template<class T> //T is a builtin type
class data
{
public:
constexpr
data() noexcept :
x_{0,0,0,0}
{}
constexpr
data(const T& a, const T& b, const T& c, const T& d) noexcept :
x_{a,b,c,d}
{}
data(const data&) noexcept = default;
data& operator = (const data&) noexcept = default;
constexpr const T&
operator[] (std::size_t i) const noexcept {
return x_[i];
}
T&
operator[] (std::size_t i) noexcept {
return x_[i];
}
private:
T x_[4];
};
template<class Ostream, class T>
Ostream& operator << (Ostream& os, const data<T>& d)
{
return (os << d[0] <<' '<< d[1] <<' '<< d[2] <<' '<< d[3]);
}
template<class T>
inline constexpr
data<T>
get_data(const T& x, const T& y)
{
return data<T>{x + y, x * y, x*x, y*y};
}
int main()
{
double x, y;
std::cin >> x >> y;
auto d = data<double>{x, y, 2*x, 2*y};
std::cout << d << std::endl;
//THE QUESTION IS ABOUT THIS LINE
d = get_data(x,y);
d[0] += d[2];
d[1] += d[3];
d[2] *= d[3];
std::cout << d << std::endl;
return 0;
}
Regarding the marked line:
Could the values x+y, x*y, x*x, y*y be written directly to the memory of d?
Or could the return type of get_data be directly constructed in the memory of d?
I can't think of a reason to not allow such an optimization. At least not for a class that has only constexpr constructors and default copy and assignment operators.
g++ 4.7.2 elides all copy constructors in this example; it seems however that assignment is always performed (even for default assignment only - as far as I can tell from the assembly that g++ emits).
The motivation for my question is the following situation in which such an optimization would greatly simplify and improve library design.
Suppose you write performance-critical library routines using a literal class. Objects of that class will hold enough data (say 20 doubles) that copies have to be kept to a minimum.
class Literal{ constexpr Literal(...): {...} {} ...};
//nice: allows RVO and is guaranteed to not have any side effects
constexpr Literal get_random_literal(RandomEngine&) {return Literal{....}; }
//not favorable in my opinion: possible non-obvious side-effects, code duplication
//would be superfluous if said optimization were performed
void set_literal_random(RandomEngine&, Literal&) {...}
It would make for a much cleaner (functional programming style) design if I could do without the second function. But sometimes I just need to modify a long-lived Literal object and have to make sure that I don't create a new one and copy-assign it to the one I want to modify. The modification itself is cheap, the copies aren't - that's what my experiments indicate.
EDIT:
Let's suppose the optimization shall only be allowed for a class with noexcept constexpr constructors and noexcept default operator=.
Elision of default copy/move assignment operators is allowed based on the general as-if rule only. That is the compiler can do it if it can ascertain that it will have no observable effect on behaviour.
In practice the as-if rule is used in general fashion to allow optimizations at intermediate representation and assembly levels. If the compiler can inline the default constructor and assignment, it can optimize them. It won't ever use the code of the copy constructor for it, but for their default implementations it should end up with the same code.
Edit: I answered before there was the code sample. Copy/move constructors are elided based on explicit permission to the compiler to do so, therefore they are elided even if they have observable effect (printing "COPY"). Assignments can only be elided based on as-if rule, but they have observable effect (printing "ASSIGN"), so the compiler is not allowed to touch them.
Does the standard allow for the elision of the assignment operator? Not in the same way as for construction. If you have any construct d = ..., the assignment operator will be called. If ... results in an expression of the same type as d, then the appropriate copy or move assignment operator will be called.
It is theoretically possible that a trivial copy/move assignment operator could be elided. But implementations are not allowed to elide anything that you could detect being elided.
Note that this is different from actual copy/move elision, because there the standard explicitly allows the elision of any constructor, whether trivial or not. You can return a std::vector by value into a new variable, and the copy will be elided if the compiler supports it. Even though it is very easy to detect the elision. The standard gives special permission to compilers to do this.
No such permission is granted for copy/move assignment. So it could only ever "elide" something that you couldn't tell the difference about. And that's not really "elision"; that's just a compiler optimization.
Objects of that class will hold enough data (say 20 doubles) that copies have to be kept to a minimum.
There's nothing stopping you from returning that Literal type right now. You will get elision if you store the object in a new variable. And if you copy assign it to an existing variable, you won't. But that's no different from a function which returns a float that you store into an existing variable: you get a copy of the float.
So it's really up to you how much copying you want to do.
There is an important drawback to what you propose: what would happen if the constructor threw? The behaviour for that case is well defined in the standard (all data members that had already been constructed are destructed in reverse order), but how would that translate into the case where we are "constructing" the object into an already existing one?
Your example is easy because the constructor cannot throw when T = double, but this is not so in the general case. You might end up with a half destructed object and undefined behaviour would ensue, even if your constructor and assignment operator were well behaved.
Jan Hudec's answer has a very good point about the as-if rule (+1 for him).
Hence, the assignment elision is allowed -- as he said -- provided that there's no observable effect. The fact that your assignment operator outputs "ASSIGN" is enough to prevent the optimization.
Notice that the situation is different for copy/move constructors because the standard allows elision for copy/move constructor even if they have observable side effects (see 12.8/31).

Class containing auto_ptr stored in vector

In an answer to Is it safe to store objects of a class which has an std::auto_ptr as its member variable in std::vector? I stated that a class that contained an auto_ptr could be stored in a vector provided the class had a user-defined copy constructor.
There were several comment suggesting that this was not the case, so this question is an attempt to clear the issue up. Consider the following code:
#include <memory>
#include <vector>
using namespace std;
struct Z {};
struct A {
A( Z z )
: p( new Z(z) ) {}
A( const A & a )
: p( a.p.get() ? new Z( *a.p.get()) : 0 ) {}
// no assigment op or dtor defined by intent
auto_ptr <Z> p;
};
int main() {
vector <A> av;
Z z;
A a(z);
av.push_back( a );
av.push_back( A(z) );
av.clear();
}
Please examine the above & in your reply indicate where undefined
behaviour in the meaning of the C++ Standard could occur for this particular class used in this particular way. I am not interested whether the class is useful, well-behaved, sortable, or how it performs under exceptions.
Please also note that this is not a question about the validity of creating a vector of auto_ptrs - I am well aware of the issues regarding that.
Thanks all for your inputs on what in
retrospect is probably a rather silly
question. I guess I focussed too much
on the copy ctor & forgot about
assignment. The lucky winner of my
acceptance points (and points mean
prizes!) is litb for a typically
exhaustive explanation (sorry
earwicker)
Objects stored in containers are required to be "CopyConstructable" as well as "Assignable" (C++2008 23.1/3).
Your class tries to deal with the CopyConstructable requirement (though I'd argue it still doesn't meet it - I edited that argument out since it's not required and because it's arguable I suppose), but it doesn't deal with the Assignable requirement. To be Assignable (C++2008 23.1/4), the following must be true where t is a value of T and u is a value of (possibly const) T:
t = u returns a T& and t is equivalent to u
The standard also says in a note (20.4.5/3): "auto_ptr does not meet the CopyConstructible and Assignable requirements for Standard Library container elements and thus instantiating a Standard Library container with an auto_ptr results in undefined behavior."
Since you don't declare or define an assignment operator, an implicit one will be provided that uses the auto_ptr's assignment operator, which definitely makes t not equivalent to u, not to mention that it won't work at all for "const T u" values (which is what Earwicker's answer points out - I'm just pointing out the exact portion(s) of the standard).
Trying to put the list of places together that makes the example undefined behavior.
#include <memory>
#include <vector>
using namespace std;
struct Z {};
struct A {
A( Z z )
: p( new Z(z) ) {}
A( const A & a )
: p( a.p.get() ? new Z( *a.p.get()) : 0 ) {}
// no assigment op or dtor defined by intent
auto_ptr <Z> p;
};
int main() {
vector <A> av;
...
}
I will examine the lines up to the one where you instantiate the vector with your type A. The Standard has to say
In 23.1/3:
The type of objects stored in these components must meet the requirements of CopyConstructible types (20.1.3), and the additional requirements of Assignable types.
In 23.1/4 (emphasis mine):
In Table 64, T is the type used to instantiate the container, t is a value of T, and u is a value of (possibly const) T.
+-----------+---------------+---------------------+
|expression |return type |postcondition |
+-----------+---------------+---------------------+
|t = u |T& |t is equivalent to u |
+-----------+---------------+---------------------+
Table 64
In 12.8/10:
If the class definition does not explicitly declare a copy assignment operator, one is declared implicitly. The implicitly-declared copy assignment operator for a class X will have the form
X& X::operator=(const X&)
if
each direct base class B of X has a copy assignment operator whose parameter is of type const B&,
const volatile B& or B, and
for all the nonstatic data members of X that are of a class type M (or array thereof), each such class type has a copy assignment operator whose parameter is of type const M&, const volatile M& or M.
Otherwise, the implicitly declared copy assignment operator will have the form
X& X::operator=(X&)
(Note the last and second last sentence)
In 17.4.3.6/1 and /2:
In certain cases (replacement functions, handler functions, operations on types used to instantiate standard library template components), the C++ Standard Library depends on components supplied by a C++ program. If these components do not meet their requirements, the Standard places no requirements on the implementation.
In particular, the effects are undefined in the following cases:
for types used as template arguments when instantiating a template component, if the operations on the type do not implement the semantics of the applicable Requirements subclause (20.1.5, 23.1, 24.1, 26.1). Operations on such types can report a failure by throwing an exception unless otherwise specified.
Now, if you look at the specification of auto_ptr you will note it has a copy-assignment operator that takes a non-const auto_ptr. Thus, the implicitly declared copy assignment operator of your class will also take a non-const type as its parameter. If you read the above places carefully, you will see how it says that instantiating a vector with your type as written is undefined behavior.
I don't think it's necessarily the case that the above code will even compile. Surely the implementor of std::vector is at liberty to require an assignment operator to be available, from const A&?
And having just tried it, it doesn't compile on Visual Studio C++ 2008 Service Pack 1:
binary '=' : no operator found which
takes a right-hand operand of type
'const A' (or there is no acceptable
conversion)
My guess is that, on the guidance of Herb Sutter, the container classes in VC++ make every effort to impose the standard requirements on their type parameters, specifically to make it hard to use auto_ptr with them. They may have overstepped the boundaries set by the standard of course, but I seem to remember it mandating true assignment as well as true copy construction.
It does compile in g++ 3.4.5, however.
Since the regular auto_ptr semantic could suggests that the ownership is passed during the copying, I would rather use here boost::scoped_ptr. Of course the assignment operator is missing.
What about the following?
cout << av[ 0 ] << endl;
Also, conceptually, a copy should leave the item copied from unchanged. This is being violated in your implementation.
(It is quite another thing that your original code compiles fine with g++ -pedantic ... and Comeau but not VS2005.)