Document number: N3657 Date: 2013-03-19 Project: Programming Language C++, Library Working Group Reply-to: Jonathan Wakely Reply-to: Stephan T. Lavavej Reply-to: Joaquín Mª López Muñoz * Adding heterogeneous comparison lookup to associative containers (rev 4) I. Introduction This paper revises N3465 (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3465.pdf) and a later draft by Joaquín Mª López Muñoz II. Motivation And Scope The associative container lookup functions (find, lower_bound, upper_bound, equal_range) only take an argument of key_type, requiring users to construct (either implicitly or explicitly) an object of the key_type to do the lookup. This may be expensive, e.g. constructing a large object to search in a set when the comparator function only looks at one field of the object. There is strong desire among users to be able to search using other types which are comparable with the key_type. N3465 adds support for this, but changes the behaviour of some existing code. The LWG had concerns about code like the following: std::set s = /* ... */; s.find("key"); In C++11 this will construct a single std::string temporary and then compare it with elements to find the key. With the change proposed by N3465 the std::set::find() function would be an unconstrained template which would pass the const char* through to the comparator function, std::less, which would construct a std::string temporary for every comparison. The LWG considered this performance problem to be a serious issue. The template find() function would also prevent finding NULL in a container of pointers, which causes previously valid code to no longer compile, but this was seen as a less serious issue than the silent performance regression. III. Impact On The Standard This proposal modifies the associative containers in and by overloading the lookup member functions with member function templates. There are no language changes. Almost all existing C++11 code is unaffected because the member functions are not present unless new C++14 library features are used as the comparison functions. IV. Design Decisions The original proposal and its revisions describe a number of options and their tradeoffs. Stephan T. Lavavej suggested that the two problems of preserving existing behaviour and allowing heterogeneous lookups could both be solved by making the containers detect when the comparison object accepts heterogeneous arguments and only conditionally overloading the current lookup functions with template versions. The std::less specialization was enhanced to support heterogeneous comparisons by N3421 (Making Operator Functors greater<>, http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3421.htm) so when the containers use that type as the comparator they can define the new template functions. The following variation constructs no temporary std::string objects: std::set> s = /* ... */; s.find("key"); In order to allow heterogeneous lookup when using user-defined comparator functions Stephan proposed adding a tag type to the function object which the container would detect. The containers would only change their interface when the comparator function defines the tag type. The proposed protocol is to check for a nested type called is_transparent. We propose adding a type with that name to all the void specializations of function objects which were made "transparent" by N3421. Only some of those function objects are suitable for use with associative containers but the proposal is to add the type to all of them for consistency and to allow users to check that any of them is transparent. The changes below are taken from N3465 with the addition of the is_transparent tag type, and changing the Table 102 replacements to additions. V. Proposed Wording Add an unspecified tag type to all the void specializations of function objects in 20.8.4 [arithmetic.operations], for example the specialization before paragraph 8 would become: template <> struct plus { template auto operator()(T&& t, U&& u) const -> decltype(std::forward(t) + std::forward(u)); typedef unspecified is_transparent; }; Add an unspecified tag type to all the void specializations of function objects in 20.8.5 [comparisons], for example the specialization before paragraph 8 would become: template <> struct equal_to { template auto operator()(T&& t, U&& u) const -> decltype(std::forward(t) == std::forward(u)); typedef unspecified is_transparent; }; Add an unspecified tag type to all the void specializations of function objects in 20.8.6 [logical.operations], for example the specialization before paragraph 5 would become: template <> struct logical_and { template auto operator()(T&& t, U&& u) const -> decltype(std::forward(t) && std::forward(u)); typedef unspecified is_transparent; }; Add an unspecified tag type to all the void specializations of function objects in 20.8.7 [bitwise.operations], for example the specialization before paragraph 5 would become: template <> struct bit_and { template auto operator()(T&& t, U&& u) const -> decltype(std::forward(t) & std::forward(u)); typedef unspecified is_transparent; }; Modify 23.2.4 [associative.reqmts] paragraph 8 as follows: In Table 102, 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, a_tran denotes a value of X when the type X::key_compare::is_transparent exists, u denotes an identifier, [...] k denotes a value of X::key_type and c denotes a value of type X::key_compare ; kl is a value such that a is partitioned (25.4) with respect to c(r, kl), with r the key value of e and e in a; ku is a value such that a is partitioned with respect to !c(ku, r); ke is a value such that a is partitioned with respect to c(r, ke) and !c(ke, r), with c(r, ke) implying !c(ke, r) . [...] Modify table 102 in section 23.2.4 [associative.reqmts] as follows: Expression | Return type | Assertion/note/pre- /postcondition | Complexity a.find(k) iterator; returns an iterator pointing to an logarithmic const_- element with the key equivalent to iterator for k, a.end() if such an element is constant a not found a_tran.find(ke) iterator; returns an iterator pointing to an logarithmic const_- element with key r such that iterator for !c(r, ke) && !(ke, r), or constant a_tran.end() if such an element a_tran. is not found a.count(k) size_type returns the number of elements log(a.size()) with key equivalent to k + a.count(k) a_tran.count(ke) size_type returns the number of elements log(a_tran.size()) with key r such that !c(r, ke) + a_tran.count(ke) && !c(r, ke) a.lower_bound(k) iterator; returns an iterator pointing to logarithmic const_- the first element with key not less iterator for than k, a.end() if constant a. such an element is not found. a_tran.lower_bound(kl) iterator; returns an iterator pointing to logarithmic const_- the first element with key r such iterator for that !c(r, kl), or a_tran.end() if constant such an element is not found. a_tran. a.upper_bound(k) iterator; returns an iterator pointing to logarithmic const_- the first element with key greater iterator for than k, or a.end() constant a. if such an element is not found. a_tran.upper_bound(ku) iterator; returns an iterator pointing to logarithmic const_- the first element with key r such iterator for that c(ku, r), or a_tran.end() if constant such an element is not found. a_tran. a.equal_range(k) pair; a.lower_bound(k), pair for constant a a_tran.equal_range(ke) pair; a_tran.lower_bound(ke), pair for constant a_tran. Add paragraph 13 in 23.2.4 [associative.reqmts]: The member function templates find, lower_bound, upper_bound and equal_range shall not participate in overload resolution unless the type Compare::is_transparent does not exist. Add to the synopsis in 23.4.4.1 [map.overview]: template iterator find(const K& x); template const_iterator find(const K& x) const; template size_type count(const K& x) const; template iterator lower_bound(const K& x); template const_iterator lower_bound(const K& x) const; template iterator upper_bound(const K& x); template const_iterator upper_bound(const K& x) const; template pair equal_range(const K& x); template pair equal_range(const K& x) const; Add to the synopsis in 23.4.5.1 [multimap.overview]: template iterator find(const K& x); template const_iterator find(const K& x) const; template size_type count(const K& x) const; template iterator lower_bound(const K& x); template const_iterator lower_bound(const K& x) const; template iterator upper_bound(const K& x); template const_iterator upper_bound(const K& x) const; template pair equal_range(const K& x); template pair equal_range(const K& x) const; Add to the synopsis in 23.4.6.1 [set.overview]: template iterator find(const K& x); template const_iterator find(const K& x) const; template size_type count(const K& x) const; template iterator lower_bound(const K& x); template const_iterator lower_bound(const K& x) const; template iterator upper_bound(const K& x); template const_iterator upper_bound(const K& x) const; template pair equal_range(const K& x); template pair equal_range(const K& x) const; Add to the synopsis in 23.4.7.1 [multiset.overview]: template iterator find(const K& x); template const_iterator find(const K& x) const; template size_type count(const K& x) const; template iterator lower_bound(const K& x); template const_iterator lower_bound(const K& x) const; template iterator upper_bound(const K& x); template const_iterator upper_bound(const K& x) const; template pair equal_range(const K& x); template pair equal_range(const K& x) const;