This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of C++11 status.

704. MoveAssignable requirement for container value type overly strict

Section: 23.2 [container.requirements] Status: C++11 Submitter: Howard Hinnant Opened: 2007-05-20 Last modified: 2016-01-28

Priority: Not Prioritized

View all other issues in [container.requirements].

View all issues with C++11 status.

Discussion:

The move-related changes inadvertently overwrote the intent of 276(i). Issue 276(i) removed the requirement of CopyAssignable from most of the member functions of node-based containers. But the move-related changes unnecessarily introduced the MoveAssignable requirement for those members which used to require CopyAssignable.

We also discussed (c++std-lib-18722) the possibility of dropping MoveAssignable from some of the sequence requirements. Additionally the in-place construction work may further reduce requirements. For purposes of an easy reference, here are the minimum sequence requirements as I currently understand them. Those items in requirements table in the working draft which do not appear below have been purposefully omitted for brevity as they do not have any requirements of this nature. Some items which do not have any requirements of this nature are included below just to confirm that they were not omitted by mistake.

Container Requirements
X u(a)value_type must be CopyConstructible
X u(rv)array requires value_type to be CopyConstructible
a = uSequences require value_type to be CopyConstructible and CopyAssignable. Associative containers require value_type to be CopyConstructible.
a = rvarray requires value_type to be CopyAssignable. Sequences containers with propagate_on_container_move_assignment == false allocators require value_type to be MoveConstructible and MoveAssignable. Associative containers with propagate_on_container_move_assignment == false allocators require value_type to be MoveConstructible.
swap(a,u)array requires value_type to be Swappable.

Sequence Requirements
X(n)value_type must be DefaultConstructible
X(n, t)value_type must be CopyConstructible
X(i, j)Sequences require value_type to be constructible from *i. Additionally if input_iterators are used, vector and deque require MoveContructible and MoveAssignable.
a.insert(p, t)The value_type must be CopyConstructible. The sequences vector and deque also require the value_type to be CopyAssignable.
a.insert(p, rv)The value_type must be MoveConstructible. The sequences vector and deque also require the value_type to be MoveAssignable.
a.insert(p, n, t)The value_type must be CopyConstructible. The sequences vector and deque also require the value_type to be CopyAssignable.
a.insert(p, i, j)If the iterators return an lvalue the value_type must be CopyConstructible. The sequences vector and deque also require the value_type to be CopyAssignable when the iterators return an lvalue. If the iterators return an rvalue the value_type must be MoveConstructible. The sequences vector and deque also require the value_type to be MoveAssignable when the iterators return an rvalue.
a.erase(p)The sequences vector and deque require the value_type to be MoveAssignable.
a.erase(q1, q2)The sequences vector and deque require the value_type to be MoveAssignable.
a.clear()
a.assign(i, j)If the iterators return an lvalue the value_type must be CopyConstructible and CopyAssignable. If the iterators return an rvalue the value_type must be MoveConstructible and MoveAssignable.
a.assign(n, t)The value_type must be CopyConstructible and CopyAssignable.
a.resize(n)The value_type must be DefaultConstructible. The sequence vector also requires the value_type to be MoveConstructible.
a.resize(n, t)The value_type must be CopyConstructible.

Optional Sequence Requirements
a.front()
a.back()
a.push_front(t)The value_type must be CopyConstructible.
a.push_front(rv)The value_type must be MoveConstructible.
a.push_back(t)The value_type must be CopyConstructible.
a.push_back(rv)The value_type must be MoveConstructible.
a.pop_front()
a.pop_back()
a[n]
a.at[n]

Associative Container Requirements
X(i, j)If the iterators return an lvalue the value_type must be CopyConstructible. If the iterators return an rvalue the value_type must be MoveConstructible.
a_uniq.insert(t)The value_type must be CopyConstructible.
a_uniq.insert(rv)The key_type and the mapped_type (if it exists) must be MoveConstructible.
a_eq.insert(t)The value_type must be CopyConstructible.
a_eq.insert(rv)The key_type and the mapped_type (if it exists) must be MoveConstructible.
a.insert(p, t)The value_type must be CopyConstructible.
a.insert(p, rv)The key_type and the mapped_type (if it exists) must be MoveConstructible.
a.insert(i, j)If the iterators return an lvalue the value_type must be CopyConstructible. If the iterators return an rvalue the key_type and the mapped_type (if it exists) must be MoveConstructible..

Unordered Associative Container Requirements
X(i, j, n, hf, eq)If the iterators return an lvalue the value_type must be CopyConstructible. If the iterators return an rvalue the value_type must be MoveConstructible.
a_uniq.insert(t)The value_type must be CopyConstructible.
a_uniq.insert(rv)The key_type and the mapped_type (if it exists) must be MoveConstructible.
a_eq.insert(t)The value_type must be CopyConstructible.
a_eq.insert(rv)The key_type and the mapped_type (if it exists) must be MoveConstructible.
a.insert(p, t)The value_type must be CopyConstructible.
a.insert(p, rv)The key_type and the mapped_type (if it exists) must be MoveConstructible.
a.insert(i, j)If the iterators return an lvalue the value_type must be CopyConstructible. If the iterators return an rvalue the key_type and the mapped_type (if it exists) must be MoveConstructible..

Miscellaneous Requirements
map[lvalue-key]The key_type must be CopyConstructible. The mapped_type must be DefaultConstructible and MoveConstructible.
map[rvalue-key]The key_type must be MoveConstructible. The mapped_type must be DefaultConstructible and MoveConstructible.

[ Kona (2007): Howard and Alan to update requirements table in issue with emplace signatures. ]

[ Bellevue: This should be handled as part of the concepts work. ]

[ 2009-07-20 Reopened by Howard: ]

This is one of the issues that was "solved by concepts" and is now no longer solved.

In a nutshell, concepts adopted the "minimum requirements" philosophy outlined in the discussion of this issue, and enforced it. My strong suggestion is that we translate the concepts specification into documentation for the containers.

What this means for vendors is that they will have to implement container members being careful to only use those characteristics of a type that the concepts specification formally allowed. Note that I am not talking about enable_if'ing everything. I am simply suggesting that (for example) we tell the vendor he can't call T's copy constructor or move constructor within the emplace member function, etc.

What this means for customers is that they will be able to use types within C++03 containers which are sometimes not CopyConstructible, and sometimes not even MoveConstructible, etc.

[ 2009-10 Santa Cruz: ]

Leave open. Howard to provide wording.

[ 2010-02-06 Howard provides wording. ]

[ 2010-02-08 Moved to Tentatively Ready after 5 positive votes on c++std-lib. ]

[ 2010-02-10 Howard opened. I neglected to reduce the requirements on value_type for the insert function of the ordered and unordered associative containers when the argument is an rvalue. Fixed it. ]

[ 2010-02-11 Moved to Tentatively Ready after 5 positive votes on c++std-lib. ]

[ 2010-03-08 Nico opens: ]

I took the task to see whether 868(i) is covered by 704 already. However, by doing that I have the impression that 704 is a big mistake.

Take e.g. the second change of 868(i):

Change 23.3.5.2 [deque.cons] para 5:

Effects: Constructs a deque with n default constructed elements.

where "default constructed" should be replaced by "value-initialized". This is the constructor out of a number of elements:

ContType c(num)

704 says:

Remove the entire section 23.3.5.2 [deque.cons].

[ This section is already specified by the requirements tables. ]

BUT, there is no requirement table that lists this constructor at all, which means that we would lose the entire specification of this function !!!

In fact, I found with further investigation, if we follow 704 to remove 23.3.2.1 we

because all these guarantees are given in the removed section but nowhere else (as far as I saw).

Looks to me that 704 need a significant review before we take that change, because chances are high that there are similar flaws in other proposed changes there (provided I am not missing anything).

[ 2010 Pittsburgh: ]

Removed the parts from the proposed wording that removed existing sections, and set to Ready for Pittsburgh.

Rationale:

[ post San Francisco: ]

Solved by N2776.

This rationale is obsolete.

Proposed resolution:

Change 23.2.2 [container.requirements.general]/4:

4 In Tables 91 and 92, X denotes a container class containing objects of type T, a and b denote values of type X, u denotes an identifier, r denotes an lvalue or a const rvalue of type X, and rv denotes a non-const rvalue of type X.

Change the following rows in Table 91 — Container requirements 23.2.2 [container.requirements.general]:

Table 91 — Container requirements
Expression Return type Assertion/note
pre-/post-condition
Complexity
X::value_type T compile time

Change 23.2.2 [container.requirements.general]/10:

Unless otherwise specified (see 23.2.4.1, 23.2.5.1, 23.3.2.3, and 23.3.6.4) all container types defined in this Clause meet the following additional requirements:

Insert a new paragraph prior to 23.2.2 [container.requirements.general]/14:

14 ...

Add to 23.2.2 [container.requirements.general]/14:

14 In Table 93, X denotes an allocator-aware container class with a value_type of T using allocator of type A, u denotes a variable, t denotes an lvalue or a const rvalue of type X, rv denotes a non-const rvalue of type X, m is a value of type A, and Q is an allocator type.

Change or add the following rows in Table 93 — Allocator-aware container requirements in 23.2.2 [container.requirements.general]:

Table 93 — Allocator-aware container requirements
Expression Return type Assertion/note
pre-/post-condition
Complexity
X(t, m)
X u(t, m);

post: u == t,
get_allocator() == m
linear
X(rv, m)
X u(rv, m);

post: u shall have the same elements, or copies of the elements, that rv had before this construction,
get_allocator() == m
constant if m == rv.get_allocator(), otherwise linear

Change the following rows in Table 94 — Sequence container requirements (in addition to container) in 23.2.4 [sequence.reqmts]:

Table 94 — Sequence container requirements (in addition to container)
Expression Return type Assertion/note
pre-/post-condition
X(i, j)
X a(i, j)
Requires: If the iterator's dereference operation returns an lvalue or a const rvalue, T shall be CopyConstructible.

Each iterator in the range [i,j) shall be dereferenced exactly once.
post: size() == distance between i and j
Constructs a sequence container equal to the range [i, j)
a = il; X&
a = X(il);

return *this;
a.emplace(p, args); iterator Requires: ConstructibleAsElement<A, T, Args>. Inserts an object of type T constructed with std::forward<Args>(args)... .
a.insert(p, t); iterator Requires: ConstructibleAsElement<A, T, Args> and T shall be CopyAssignable. Inserts a copy t before p.
a.insert(p, rv); iterator Requires: ConstructibleAsElement<A, T, T&&> and T shall be MoveAssignable. Inserts a copy rv before p.
a.insert(p, i, j) iterator Requires: If the iterator's dereference operation returns an lvalue or a const rvalue, T shall be CopyConstructible.

Each iterator in the range [i,j) shall be dereferenced exactly once.
pre: i and j are not iterators into a.
Inserts copies of elements in [i, j) before p
a.erase(q); iterator Requires: T and T shall be MoveAssignable. Erases the element pointed to by q.
a.erase(q1, q2); iterator Requires: T and T shall be MoveAssignable. Erases the elements in the range [q1, q2).
a.clear(); void erase(begin(), end())
post: size() == 0
a.assign(i, j) void Requires: If the iterator's dereference operation returns an lvalue or a const rvalue, T shall be CopyConstructible and CopyAssignable.
Each iterator in the range [i,j) shall be dereferenced exactly once.
pre: i, j are not iterators into a.
Replaces elements in a with a copy of [i, j).

Change the following rows in Table 95 — Optional sequence container operations in 23.2.4 [sequence.reqmts]:

Table 95 — Optional sequence container operations
Expression Return type Operational semantics Container
a.emplace_front(args) void a.emplace(a.begin(), std::forward<Args>(args)...)

Requires: ConstructibleAsElement<A, T, Args>
list, deque, forward_list
a.emplace_back(args) void a.emplace(a.end(), std::forward<Args>(args)...)

Requires: ConstructibleAsElement<A, T, Args>
list, deque, vector
a.push_front(t) void a.insert(a.begin(), t)

Requires: ConstructibleAsElement<A, T, T> and T shall be CopyAssignable.
list, deque, forward_list
a.push_front(rv) void a.insert(a.begin(), t)

Requires: ConstructibleAsElement<A, T, T&&> and T shall be MoveAssignable.
list, deque, forward_list
a.push_back(t) void a.insert(a.end(), t)

Requires: ConstructibleAsElement<A, T, T> and T shall be CopyAssignable.
vector, list, deque, basic_string
a.push_back(rv) void a.insert(a.end(), t)

Requires: ConstructibleAsElement<A, T, T&&> and T shall be MoveAssignable.
vector, list, deque, basic_string
a.pop_front() void a.erase(a.begin())

list, deque, forward_list
a.pop_back() void { iterator tmp = a.end();
--tmp;
a.erase(tmp); }


vector, list, deque, basic_string

Insert a new paragraph prior to 23.2.7 [associative.reqmts]/7, and edit paragraph 7:

7 In Table 96, X denotes an associative container class, a denotes a value of X, a_uniq denotes a value of X when X supports unique keys, a_eq denotes a value of X when X supports multiple keys, u denotes an identifier, r denotes an lvalue or a const rvalue of type X, rv denotes a non-const rvalue of type X, i and j satisfy input iterator requirements and refer to elements implicitly convertible to value_type, [i,j) denotes a valid range, p denotes a valid const iterator to a, q denotes a valid dereferenceable const iterator to a, [q1, q2) denotes a valid range of const iterators in a, il designates an object of type initializer_list<value_type>, t denotes a value of X::value_type, k denotes a value of X::key_type and c denotes a value of type X::key_compare. A denotes the storage allocator used by X, if any, or std::allocator<X::value_type> otherwise, and m denotes an allocator of a type convertible to A.

Change or add the following rows in Table 96 — Associative container requirements (in addition to container) in 23.2.7 [associative.reqmts]:

Table 96 — Associative container requirements (in addition to container)
Expression Return type Assertion/note
pre-/post-condition
Complexity
X::key_type Key Key is CopyConstructible and CopyAssignable compile time
X(c)
X a(c);
Requires: ConstructibleAsElement<A, key_compare, key_compare>.

Constructs an empty container.
Uses a copy of c as a comparison object.
constant
X()
X a;
Requires: ConstructibleAsElement<A, key_compare, key_compare>.

Constructs an empty container.
Uses Compare() as a comparison object.
constant
X(i, j, c)
X a(i, j, c);
Requires: ConstructibleAsElement<A, key_compare, key_compare>.

Constructs an empty container ans inserts elements from the range [i, j) into it; uses c as a comparison object.
N log N in general (N is the distance from i to j); linear if [i, j) is sorted with value_comp()
X(i, j)
X a(i, j);
Requires: ConstructibleAsElement<A, key_compare, key_compare>.

Same as above, but uses Compare() as a comparison object.
same as above
a = il X& a = X(il);
return *this;


Same as a = X(il).
a_uniq.emplace(args) pair<iterator, bool>
inserts a T object t constructed with std::forward<Args>(args)... if and only if there is no element in the container with key equivalent to the key of t. The bool component of the returned pair is true if and only if the insertion takes place, and the iterator component of the pair points to the element with key equivalent to the key of t.
logarithmic
a_eq.emplace(args) iterator
inserts a T object t constructed with std::forward<Args>(args)... and returns the iterator pointing to the newly inserted element.
logarithmic
a_uniq.insert(t) pair<iterator, bool>
inserts t if and only if there is no element in the container with key equivalent to the key of t. The bool component of the returned pair is true if and only if the insertion takes place, and the iterator component of the pair points to the element with key equivalent to the key of t.
logarithmic
a_eq.insert(t) iterator
inserts t and returns the iterator pointing to the newly inserted element. If a range containing elements equivalent to t exists in a_eq, t is inserted at the end of that range.
logarithmic
a.insert(p, t) iterator
inserts t if and only if there is no element with key equivalent to the key of t in containers with unique keys; always inserts t in containers with equivalent keys; always returns the iterator pointing to the element with key equivalent to the key of t. t is inserted as close as possible to the position just prior to p.
logarithmic in general, but amortized constant if t is inserted right before p.
a.insert(i, j) void
pre: i, j are not iterators into a. inserts each element from the range [i,j) if and only if there is no element with key equivalent to the key of that element in containers with unique keys; always inserts that element in containers with equivalent keys.
N log(size() + N ) (N is the distance from i to j)

Insert a new paragraph prior to 23.2.8 [unord.req]/9:

9 ...

Change or add the following rows in Table 98 — Unordered associative container requirements (in addition to container) in 23.2.8 [unord.req]:

Table 98 — Unordered associative container requirements (in addition to container)
Expression Return type Assertion/note
pre-/post-condition
Complexity
X::key_type Key Key shall be CopyAssignable and CopyConstructible compile time
X(n, hf, eq)
X a(n, hf, eq)
X Constructs an empty container with at least n buckets, using hf as the hash function and eq as the key equality predicate. O(N)
X(n, hf)
X a(n, hf)
X Constructs an empty container with at least n buckets, using hf as the hash function and key_equal() as the key equality predicate. O(N)
X(n)
X a(n)
X Constructs an empty container with at least n buckets, using hasher() as the hash function and key_equal() as the key equality predicate. O(N)
X()
X a
X Constructs an empty container an unspecified number of buckets, using hasher() as the hash function and key_equal() as the key equality predicate. constant
X(i, j, n, hf, eq)
X a(i, j, n, hf, eq)
X
Constructs an empty container with at least n buckets, using hf as the hash function and eq as the key equality predicate, and inserts elements from [i, j) into it.
Average case O(N) (N is distance(i, j)), worst case O(N2)
X(i, j, n, hf)
X a(i, j, n, hf)
X
Constructs an empty container with at least n buckets, using hf as the hash function and key_equal() as the key equality predicate, and inserts elements from [i, j) into it.
Average case O(N) (N is distance(i, j)), worst case O(N2)
X(i, j, n)
X a(i, j, n)
X
Constructs an empty container with at least n buckets, using hasher() as the hash function and key_equal() as the key equality predicate, and inserts elements from [i, j) into it.
Average case O(N) (N is distance(i, j)), worst case O(N2)
X(i, j)
X a(i, j)
X
Constructs an empty container with an unspecified number of buckets, using hasher() as the hash function and key_equal() as the key equality predicate, and inserts elements from [i, j) into it.
Average case O(N) (N is distance(i, j)), worst case O(N2)
X(b)
X a(b)
X Copy constructor. In addition to the contained elements , copies the hash function, predicate, and maximum load factor. Average case linear in b.size(), worst case quadratic.
a = b X& Copy assignment operator. In addition to the contained elements , copies the hash function, predicate, and maximum load factor. Average case linear in b.size(), worst case quadratic.
a = il X& a = X(il); return *this;

Average case linear in il.size(), worst case quadratic.
a_uniq.emplace(args) pair<iterator, bool>
inserts a T object t constructed with std::forward<Args>(args)... if and only if there is no element in the container with key equivalent to the key of t. The bool component of the returned pair is true if and only if the insertion takes place, and the iterator component of the pair points to the element with key equivalent to the key of t.
Average case O(1), worst case O(a_uniq.size()).
a_eq.emplace(args) iterator
inserts a T object t constructed with std::forward<Args>(args)... and returns the iterator pointing to the newly inserted element.
Average case O(1), worst case O(a_eq.size()).
a.emplace_hint(p, args) iterator
equivalent to a.emplace( std::forward<Args>(args)...). Return value is an iterator pointing to the element with the key equivalent to the newly inserted element. The const_iterator p is a hint pointing to where the search should start. Implementations are permitted to ignore the hint.
Average case O(1), worst case O(a.size()).
a_uniq.insert(t) pair<iterator, bool>
Inserts t if and only if there is no element in the container with key equivalent to the key of t. The bool component of the returned pair indicates whether the insertion takes place, and the iterator component points to the element with key equivalent to the key of t.
Average case O(1), worst case O(a_uniq.size()).
a_eq.insert(t) iterator
Inserts t, and returns an iterator pointing to the newly inserted element.
Average case O(1), worst case O(a_uniq.size()).
a.insert(q, t) iterator
Equivalent to a.insert(t). Return value is an iterator pointing to the element with the key equivalent to that of t. The iterator q is a hint pointing to where the search should start. Implementations are permitted to ignore the hint.
Average case O(1), worst case O(a_uniq.size()).
a.insert(i, j) void
Pre: i and j are not iterators in a. Equivalent to a.insert(t) for each element in [i,j).
Average case O(N), where N is distance(i, j). Worst case O(N * a.size()).

Change [forwardlist]/2:

2 A forward_list satisfies all of the requirements of a container (table 91), except that the size() member function is not provided. Descriptions are provided here only for operations on forward_list that are not described in that table or for operations where there is additional semantic information.

Add a new paragraph after [forwardlist.modifiers]/23:

void clear();

23 Effects: Erases all elements in the range [begin(),end()).

Change 23.3.13.3 [vector.capacity]/13:

void resize(size_type sz, const T& c);

13 Requires: If value_type has a move constructor, that constructor shall not throw any exceptions.

In 23.5.6 [unord.set] and 23.5.7 [unord.multiset] substitute "Key" for "Value".

[ The above substitution is normative as it ties into the requirements table. ]