This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of Open status.
Section: 23.2.7 [associative.reqmts] Status: Open Submitter: Juan Soulie Opened: 2012-12-19 Last modified: 2019-04-23
Priority: 3
View other active issues in [associative.reqmts].
View all other issues in [associative.reqmts].
View all issues with Open status.
Discussion:
Table 102 in 23.2.7 [associative.reqmts]/8 states on expression a.key_comp() that it
"returns the comparison object out of which a was constructed". At the same time,
23.2.2 [container.requirements.general]/8 states (starting in the third line) that
"...Any Compare, Pred, or Hash objects belonging to a and b
shall be swappable and shall be exchanged by unqualified calls to non-member swap...". This is
problematic for any compliant implementation, since once swapped the container cannot return the comparison
object out of which it was constructed unless incurring in storing an otherwise needless object.
hasher
and key_equal members of unordered containers (they propagate), but it says nothing about stateful
comparison objects of (ordered) associative containers, except for the statement in
23.2.2 [container.requirements.general]/8 referred above and only related to swap.
For example, it is unclear to me what is specified to happen on an assignment: should the comparison object
be copied/moved along with the elements, or should the left-hand side object keep its own?
Maybe this has been intentionally left unspecified with the purpose of compatibility with C++98, which I
understand it specified that comparison objects were kept for the entire life of the container (like allocators)
— an unfortunate choice. But anyway, the segment of 23.2.2 [container.requirements.general] quoted
above seems to break any possible backwards compatibility with C++98 in this regard.
Therefore, taking into consideration consistency with how this is dealed with for unordered associative
containers, I propose that Table 102 is modified as follows:
The row for expression a.key_comp() is changed so that its "assertion/note pre-/post-condition" reads
"Returns a's comparison object."
A new row is added at the appropriate location (which I believe would be after "X(il)" row), with:
Table 102 — Associative container requirements (in addition to container) Expression Return type Assertion/note pre-/post-condition Complexity X(b)
X a(b)XCopy constructor. In addition to
the requirements of Table 96, copies
the comparison object.Linear in b.size()a = bX&Copy assignment operator. In addition to
the requirements of Table 96, copies the
comparison object.Linear in a.size()andb.size()
[2013-03-15 Issues Teleconference]
Moved to Review.
[2013-04-18, Bristol]
STL: can't believe we don't specify this already. this is totally necessary
Alisdair: how does it do this? copy construction? assignment? Also need it for move. STL: we already specify this for constructing from a comparator, not during copy construction though. Jonathan: don't like wording, should say "key_compare is CopyConstructible. Uses b.key_comp()
as a comparison object."
STL: we get it right for unordered!
Jonathan: can't wordsmith this now, but I think implementations do the right thing.
Alisdair: not sure what right thing is for moves. Also we say nothing about propagating allocators to functors.
Moved to Open.
[2015-02 Cologne]
TK: There's no need for fine-grained propagate/not-propagate control. If you don't want to propagate the predicate, you can simply construct or insert from an iterator range.
VV: libstdc++ already implements the resolution of this issue. GR: There are a couple of other problems. We don't specify move constructor and move assignment for maps. Those are just general. TK: General container requirements already describe the semantics for {copy,move}-{construction,assignment}, so it doesn't seem that there's room for choice instd::map assignments. unordered_map is different, though.
[Note: Check what general container requirements say about container equality.]
DK will draft wording. The decision is to unambiguously make all {copy,move}-{construction,assignment} operations endow the
LHS with the exact state of the RHS, including all predicates and hash function states.
Conclusion: Update wording, revisit later.
[2015-05-06 Lenexa: Waiting for updated wording]
Previous resolution [SUPERSEDED]:
This wording is relative to N3485.
Change Table 102 as indicated:
Table 102 — Associative container requirements (in addition to container) Expression Return type Assertion/note pre-/post-condition Complexity …X(il)Same as X(il.begin(), il.end()).same as X(il.begin(), il.end()).X(b)
X a(b)Copy constructor. In addition to
the requirements of Table 96, copies
the comparison object.Linear in b.size()a = bX&Copy assignment operator. In addition to
the requirements of Table 96, copies the
comparison object.Linear in a.size()andb.size()…a.key_comp()X::key_comparerReturnsthea's comparison object
out of which a was constructed.constant
[2015-10-19 Daniel comments and provides alternative wording]
The current standard is especially unclear in regard to what effects move operations of unordered/associative containers should have. We have one example that is standardized exactly in this way by looking at 23.6.4.3 [priqueue.cons.alloc] p7:
template <class Alloc> priority_queue(priority_queue&& q, const Alloc& a);-7- Effects: Initializes
cwithstd::move(q.c)as the first argument andaas the second argument, and initializescompwithstd::move(q.comp)
A similarly comparable example are the move-operations of std::unique_ptr in regard to the deleter
(when this is no a reference), which also respect move-capabilities of that function object.
When an associative container is constructed by passing a comparison object the container shall not store a pointer or reference to the passed object, even if that object is passed by reference. When an associative container is copied, either through a copy constructor or an assignment operator, the target container shall then use the comparison object from the container being copied, as if that comparison object had been passed to the target container in its constructor.
The second sentence of this wording is problematic for several reasons:
It only talks about copy operations, not about move operations, except that the term "assignment" without leading "copy" is a bit ambigious (albeit it seems clear in the complete context).
It is not really clear how to interpret "as if that comparison object had been passed to the target container in its constructor" for an assignment operation. A possible but not conclusive interpretation could be that this is wording supporting a "copy-via-swap" idiom.
There does not exist similar wording for unordered containers, except that Table 102 provides entries for copy construction and copy assignment of the containers whose wording just talks of "copies" in either case.
Existing implementations differ already:
Visual Studio 2015 uses copy construction and copy assignment for the two copy operations but uses swap operations for the move operations.
GCC's libstdc++ performs copy construction and copy assignment for the two copy operations and for the two move operations, respectively
clang++'s libc++ performs copy/move construction and copy/move assignment for the corresponding four copy/move operations
The alternative wording provided below attempts to clarify that container copy/move operations perform the corresponding copy/move operations on the owned function objects.
In addition the wording also resolves LWG 2215(i): I believe that the current wording should require that container function objects should meet theCopyConstructible requirements. Adding
this general requirement also fixes the underspecified requirements of the accessor functions key_comp() and
value_comp().
I don't think that a general requirement for Swappable is needed, only the member swap function currently requires this.
Nonetheless the wording below does support stateful functors that are also moveable or move-assignable,
therefore the specified semantics in terms of move operations.
I should add the following warning, though: If this proposed wording would be accepted, there is a little chance of
code breakage, because the current wording can be read that in general there is no requirement that the
container functors are CopyConstructible. The following code example is accepted by gcc + libstd++:
#include <map>
#include <utility>
#include <iostream>
struct Cmp {
Cmp() = default;
Cmp(const Cmp&) = delete;
Cmp(Cmp&&) = delete;
Cmp& operator=(const Cmp&) = delete;
Cmp& operator=(Cmp&&) = delete;
template<class T>
bool operator()(const T& x, const T& y) const
{
return x < y;
}
};
typedef std::map<int, int, Cmp> MyMap;
int main() {
MyMap m;
std::cout << (m.find(12) == m.end()) << std::endl;
}
Previous resolution [SUPERSEDED]:
This wording is relative to N4527.
Change 23.2.7 [associative.reqmts] p8 as indicated:
-8- In Table 101,
Xdenotes an associative container class,adenotes a value of typeX,bdenotes a possiblyconstvalue of typeX,rvdenotes a non-constrvalue of typeX,udenotes the name of a variable being declared, […]Change Table 101 as indicated:
Table 101 — Associative container requirements (in addition to container) Expression Return type Assertion/note pre-/post-condition Complexity …X::key_compareCompareRequires: CompareisCopyConstructible.
defaults toless<key_type>compile time X(c)
X u(c);Requires:Effects: Constructs an empty container.key_compareisCopyConstructible.
Uses a copy ofcas a comparison object.[…] …X(i,j,c)
X u(i,j,c);Requires: key_compareisCopyConstructible.value_typeisEmplaceConstructibleintoXfrom*i.
Effects: Constructs an empty container and inserts elements
from the range[i, j)into it; usescas a comparison object.[…] …X(il)Same as X(il.begin(), il.end()).same as X(il.begin(), il.end()).X(b)
X a(b)(In addition to the requirements of Table 95)
Effects: Copy constructs the comparison object ofafrom
the comparison object ofb.Linear in b.size()X(rv)
X a(rv)(In addition to the requirements of Table 95 and Table 98)
Effects: Move constructs the comparison object ofafrom
the comparison object ofrv.constant a = bX&(In addition to the requirements of Table 95 and Table 98)
Requires:key_compareisCopyAssignable.
Effects: Copy assigns the comparison object ofb
to the comparison object ofa.Linear in a.size()andb.size()a = rvX&(In addition to the requirements of Table 95 and Table 98)
Requires:key_compareisMoveAssignable.
Effects: Move assigns from the comparison object ofrv
to the comparison object ofa.Linear …a.key_comp()X::key_comparerReturnsthea's comparison object
out of which a was constructed.constant Change 23.2.7 [associative.reqmts] p12 as indicated:
-12- When an associative container is constructed by passing a comparison object the container shall not store a pointer or reference to the passed object, even if that object is passed by reference.
When an associative container is copied, either through a copy constructor or an assignment operator, the target container shall then use the comparison object from the container being copied, as if that comparison object had been passed to the target container in its constructor.Change 23.2.8 [unord.req] p11 as indicated:
-11- In Table 102:
Xdenotes an unordered associative container class,adenotes a value of typeX,bdenotes a possiblyconstvalue of typeX,rvdenotes a non-constrvalue of typeX, […]Change Table 102 as indicated:
Table 102 — Unordered associative container requirements (in addition to container) Expression Return type Assertion/note pre-/post-condition Complexity …X::hasherHashRequires: HashisCopyConstructible.
Hashshall be a unary function object type
such that the expressionhf(k)has typestd::size_t.compile time X::key_equalPredRequires: PredisCopyConstructible.
Predshall be a binary predicate that takes
two arguments of typeKey.
Predis an equivalence relation.compile time …X(n, hf, eq)
X a(n, hf, eq)XRequires:Effects: […]hasherandkey_equalareCopyConstructible.[…] X(n, hf)
X a(n, hf)XRequires: hasherisCopyConstructibleandkey_equalisDefaultConstructible.
Effects: […][…] …X(i, j, n, hf, eq)
X a(i, j, n, hf, eq)XRequires: hasherandkey_equalareCopyConstructible.value_typeisEmplaceConstructibleintoXfrom*i.
Effects: […][…] X(i, j, n, hf)
X a(i, j, n, hf)XRequires: hasherisCopyConstructibleandkey_equalisDefaultConstructible.
value_typeisEmplaceConstructibleintoXfrom*i.
Effects: […][…] …X(b)
X a(b)XCopy constructor. In addition(In addition to the requirements of Table 95)
to the requirements of Table 95,
copies the hash function,
predicate, and maximum load
factor.
Effects: Copy constructs the hash function, predicate, and maximum load factor
ofafrom the corresponding objects ofb.Average case linear in
b.size(),
worst case quadratic.X(rv)
X a(rv)X(In addition to the requirements of Table 95 and Table 98)
Effects: Move constructs the hash function, predicate, and maximum load factor
ofafrom the corresponding objects ofrv.constant a = bX&Copy assignment operator. In(In addition to the requirements of Table 95 and Table 98)
addition to the requirements of
Table 95, copies the hash
function, predicate, and
maximum load factor.
Requires:hasherandkey_equalareCopyAssignable.
Effects: Copy assigns the hash function, predicate, and maximum load factor
ofbto the corresponding objects ofa.Average case linear in
b.size(),
worst case quadratic.a = rvX&(In addition to the requirements of Table 95 and Table 98)
Requires:hasherandkey_equalareMoveAssignable.
Effects: Move assigns the hash function, predicate, and maximum load factor
fromrvto the corresponding objects ofa.Linear …
[2016-08-07]
Daniel removes the previously proposed wording to work on revised wording.
[2019-04-22, Billy comments]
In addition to the Cpp17CopyConstructible discussion going on there, I think we need to require that
calling the comparison function when Compare itself is const needs to produce the same answer
as if Compare is non-const.
Proposed resolution: