1. Changelog
-
R0
-
First submission
-
2. Tony Tables
Before | After |
---|---|
|
|
|
|
|
|
3. Motivation and Scope
Smart pointer classes are universally recognized as the idiomatic way to express ownership of a resource (very incomplete list: [Sutter], [Meyers], [R.20]). On the other hand, raw pointers (and references) are supposed to be used as non-owning types to access a resource.
Both smart pointers and raw pointers, as their name says, share a common semantic: representing the address of an object.
This semantic comes with a set of meaningful operations; for instance, asking
if two (smart) pointers represent the address of the same object.
is used to express this intent.
Indeed, with the owning smart pointer class templates available in the Standard
Library (
and
), one can already use
between two smart pointer objects (of the same class). However one cannot use
it between a smart pointer and a raw pointer, because the Standard Library is
lacking that set of overloads; instead, one has to manually extract the raw
pointer out of the smart pointer class:
std :: shared_ptr < object > sptr1 , sptr2 ; object * rawptr ; // Do both pointers refer to the same object? if ( sptr1 == sptr2 ) { ~~~ } // WORKS if ( sptr1 == rawptr ) { ~~~ } // ERROR, no such operator if ( sptr1 . get () == rawptr ) { ~~~ } // WORKS; but why the extra syntax?
This discussion can be easily generalized to the full set of the six relational operations; these operations have already well-established semantics, and are indeed already defined between smart pointers objects (of the same class) or between raw pointers, but they are not supported in mixed scenarios.
Allowing mixed comparisons isn’t merely a "semantic fixup"; the situation where one has to compare smart pointers and raw pointers commonly occurs in practice (the typical use case is outlined in the first example in the § 2 Tony Tables above, where a "manager" object gives non-owning raw pointers to clients, and the clients pass these raw pointers back to the manager, and now the manager needs to do mixed comparisons).
3.1. Associative containers
Moreover, we believe that allowing mixed comparisons is useful in order to streamline heterogeneous comparison in associative containers for smart pointer classes.
The case of an associative container using a
as its key
type is particularly annoying; one cannot practically ever look up in such a
container using another
, as that would imply having two
objects owning the same object. Instead, the typical lookup is
heterogeneous (by raw pointer); this proposal is one step towards making it
more convenient to use, because it enables the usage of the standard
or
.
We however are not addresssing at all the issue of heterogeneous hashing for
smart pointers. While likely very useful in general, heterogeneous hashing can
be tackled separately by another proposal that builds on top of this one (for
instance, by making the
specializations for Standard smart pointers
1) transparent, and 2) able to hash the smart pointer’s
/
as well as the smart pointer object itself. But more research
and field experience is certainly needed.)
4. Impact On The Standard
This proposal is a pure library extension. It proposes changes to an existing
header,
, but it does not require changes to any standard classes or
functions and it does not require changes to any of the standard requirement
tables. The impact is positive: code that was ill-formed before becomes
well-formed.
This proposal does not depend on any other library extensions.
This proposal does not require any changes in the core language.
[P0805R2] is vaguely related to this proposal. It proposes to add mixed
comparisons between containers of the same type (for instance, to be able to
compare a
with a
), without resorting to manual
calls to algorithms; instead, one can use a comparison operator. A quite
verbose call to
can therefore be replaced by a much simpler
. In this sense, [P0805R2] matches the spirit of the current proposal, although comparing
smart pointers and raw pointer does not require any algorithm, and does not
have such a verbose syntax.
5. Design Decisions
5.1. Should unique_ptr
have the full set of ordering operators (<
, <=
, >
, >=
), or just <=>
?
[P1614R2] added support for
across the Standard Library.
Notably, it added
for
, leaving the other
four ordering operators (
,
,
,
) untouched. On the
other hand, when looking at
, the same paper replaced these four
operators with
.
We believe that this was done in order to preserve the semantics for the
existing operators, which are defined in terms of customization points
(notably,
;
can work in terms of a custom "fancy"
pointer type). We are not bound by any pre-existing semantics, so we are just
proposing
for
.
What does LEWG(I) think about this?
6. Technical Specifications
All the proposed changes are relative to [N4868].
6.1. Feature testing macro
Add to the list in [version.syn]:
#define __cpp_lib_mixed_smart_pointer_comparisons YYYYMML // also in <memory>
with the value specified as usual (year and month of adoption).
6.2. Proposed wording
6.2.1. Synopsis
Modify [memory.syn] as shown:
template < class T1 , class D1 , class T2 , class D2 > requires three_way_comparable_with < typename unique_ptr < T1 , D1 >:: pointer , typename unique_ptr < T2 , D2 >:: pointer > compare_three_way_result_t < typename unique_ptr < T1 , D1 >:: pointer , typename unique_ptr < T2 , D2 >:: pointer > operator <=> ( const unique_ptr < T1 , D1 >& x , const unique_ptr < T2 , D2 >& y ); template < class T1 , class D , class T2 > requires equality_comparable_with < typename unique_ptr < T1 , D >:: pointer , T2 > bool operator == ( const unique_ptr < T1 , D >& x , const T2 & y ); template < class T1 , class D , class T2 > requires three_way_comparable_with < typename unique_ptr < T1 , D >:: pointer , T2 > compare_three_way_result_t < typename unique_ptr < T1 , D >:: pointer , T2 > operator <=> ( const unique_ptr < T1 , D >& x , const T2 & y ); [...] // [util.smartptr.shared.cmp], shared_ptr comparisons template < class T , class U > bool operator == ( const shared_ptr < T >& a , const shared_ptr < U >& b ) noexcept ; template < class T , class U > strong_ordering operator <=> ( const shared_ptr < T >& a , const shared_ptr < U >& b ) noexcept ; template < class T1 , class T2 > requires equality_comparable_with < typename shared_ptr < T1 >:: element_type * , T2 > bool operator == ( const shared_ptr < T1 >& a , const T2 & b ); template < class T1 , class T2 > requires three_way_comparable_with < typename shared_ptr < T1 >:: element_type * , T2 > compare_three_way_result_t < typename shared_ptr < T1 >:: element_type * , T2 > bool operator <=> ( const shared_ptr < T1 >& a , const T2 & b ); template < class T > bool operator == ( const shared_ptr < T >& x , nullptr_t ) noexcept ; template < class T > strong_ordering operator <=> ( const shared_ptr < T >& x , nullptr_t ) noexcept ;
6.2.2. unique_ptr
In [unique.ptr.special], add after paragraph 11:
12 Constraints:template < class T1 , class D , class T2 > requires equality_comparable_with < typename unique_ptr < T1 , D >:: pointer , T2 > bool operator == ( const unique_ptr < T1 , D >& x , const T2 & y ); is not a specialization of
T2 .
unique_ptr
13 Returns:.
x . get () == y 14 Constraints:template < class T1 , class D , class T2 > requires three_way_comparable_with < typename unique_ptr < T1 , D >:: pointer , T2 > compare_three_way_result_t < typename unique_ptr < T1 , D >:: pointer , T2 > operator <=> ( const unique_ptr < T1 , D >& x , const T2 & y ); is not a specialization of
T2 .
unique_ptr
15 Returns:.
compare_three_way ()( x . get (), y )
Renumber the existing paragraphs 12-18 to 16-22.
6.2.3. shared_ptr
In [util.smartptr.shared.cmp], insert after paragraph 1:
2 Constraints:template < class T1 , class T2 > requires equality_comparable_with < typename shared_ptr < T1 >:: element_type * , T2 > bool operator == ( const shared_ptr < T1 >& a , const T2 & b ); is not a specialization of
T2 .
shared_ptr
3 Returns:.
a . get () == b
Renumber the existing paragraphs 2-4 to 4-6, and insert after the (newly numbered) paragraph 6:
7 Constraints:template < class T1 , class T2 > requires three_way_comparable_with < typename shared_ptr < T1 >:: element_type * , T2 > compare_three_way_result_t < typename shared_ptr < T1 >:: element_type * , T2 > bool operator <=> ( const shared_ptr < T1 >& a , const T2 & b ); is not a specialization of
T2 .
shared_ptr
8 Returns:.
compare_three_way ()( a . get (), b )
Renumber the existing paragraph 5 to 9.
7. Implementation experience
A working prototype of the feature described here, done on top of GCC 10.2, is available in this GCC branch on GitHub.
8. Acknowledgements
Credits for this idea go to Marc Mutz, who raised the question on the LEWG reflector, receiving a positive feedback.
Thanks to the reviewers of early drafts of this paper on the std-proposals mailing list.
Thanks to KDAB for supporting this work.
All remaining errors are ours and ours only.