operator <=>

discussion on odering design
This commit is contained in:
Marius Bancila 2018-09-05 12:50:50 +03:00
parent 3a1cf82c79
commit 0a42b9f66d

144
P0959.md
View File

@ -1,7 +1,7 @@
| | |
| ___ | ___ |
| --- | --- |
| Doc. No.: | P0959R1 |
| Date: | 2018-06-27 |
| Date: | 2018-09-05 |
| Reply to: | Marius Bancila |
| Audience: | Library WG |
| Title: | A Proposal for a Universally Unique Identifier Library |
@ -24,10 +24,12 @@ Revised with feedback from the LWG and the community.
* Added a conversion construct from `std::span<std::byte, 16>`.
* Added the member function `as_bytes()` to convert the `uuid` into a view of its underlying bytes.
* Constructing a `uuid` from a range with a size other than 16 is undefined behaviour.
* Replaced operators '==', '!=' and `<` with the three-way operator `<=>`
* Removed mutable iterators (but preserved the constant iterators).
* Removed typedefs and others container-like parts.
* Defined the correlation between the internal UUID bytes and the string representation.
* Added UUID layout and byte order specification from the RFC 4122 document.
# Added section on alternative ordering design
* Most functions are constexpr.
* Replaced typedefs with using statements.
@ -52,7 +54,6 @@ The proposed library, that should be available in a new header called `<uuid>` i
* a class called `uuid_error` representing an exception type for `uuid` operations
* function objects that generate UUIDs, called generators: `basic_uuid_random_generator<T>`, `uuid_random_generator`, `uuid_name_generator`
* conversion functions from strings `from_string()` and to strings `std::to_string()` \ `std::to_wstring()`, as well as an overloaded `operator<<` for `std::basic_ostream`
* comparison operators `==`, `!=`, `<`
* `std::swap()` overload for `uuid`
* `std::hash<>` specialization for `uuid`
@ -92,7 +93,7 @@ uuid id(std::begin(arr), std::end(arr));
### Span constructor
The conversion constructor that takes a `std::span` constructs a `uuid` from a contiguous sequence of 16 bytes.
The conversion constructor that takes a `std::span` and constructs a `uuid` from a contiguous sequence of 16 bytes.
```cpp
std::array<uuid::value_type, 16> arr{ {
@ -155,7 +156,7 @@ Because the internal representation may not be a straightforward array of bytes
### `variant` and `version`
Member functions `variant()` and `version()` allow checking the variant type of the uuid and respectively the version type. These are defined by two strongly typed enums called `uuid_variant` and `uuid_version`.
Member functions `variant()` and `version()` allow checking the variant type of the uuid and, respectively, the version type. These are defined by two strongly typed enums called `uuid_variant` and `uuid_version`.
```cpp
uuid id("47183823-2574-4bfd-b411-99ed177d3e43");
@ -163,6 +164,32 @@ assert(id.version() == uuid_version::random_number_based);
assert(id.variant() == uuid_variant::rfc);
```
### Equality and ordering
Although it does not make sense to check whether a UUID is less (or less or equal) then another UUID, the overloading of this operator for `uuid` is necessary in order to be able to store `uuid` values in containers such as `std::set` that by default use `operator <` to compare keys. Because operators `==` and `!=` are needed for checking whether two UUIDs are the same (a basic operation for an identifier type) the three-way comparison operator `<=>` is defined so that all comparison operators are provided.
```cpp
std::array<uuid::value_type, 16> arr{ {
0x47, 0x18, 0x38, 0x23,
0x25, 0x74,
0x4b, 0xfd,
0xb4, 0x11,
0x99, 0xed, 0x17, 0x7d, 0x3e, 0x43
} };
uuid id1{ std::span<uuid::value_type, 16>{arr} };
uuid id2{"47183823-2574-4bfd-b411-99ed177d3e43"};
assert(id1 == id2);
std::set<std::uuid> ids{
uuid{},
uuid("47183823-2574-4bfd-b411-99ed177d3e43")
};
assert(ids.size() == 2);
assert(ids.find(uuid{}) != ids.end());
```
### Span view
Member function `as_bytes()` converts the `uuid` into a view of its underlying bytes.
```cpp
@ -203,7 +230,7 @@ assert(empty.is_nil());
assert(!id.is_nil());
```
### string parsing
### String parsing
Static overloaded member function `from_string()` allows to create `uuid` instances from various strings.
@ -241,20 +268,6 @@ assert(id == id);
assert(empty != id);
```
### `operator<`
Although it does not make sense to check whether a UUID is less or less or equal then another UUID, the overloading of this operator for `uuid` is necessary in order to be able to store `uuid` values in containers such as `std::set` that by default use `operator <` to compare keys.
```cpp
std::set<std::uuid> ids{
uuid{},
uuid("47183823-2574-4bfd-b411-99ed177d3e43")
};
assert(ids.size() == 2);
assert(ids.find(uuid{}) != ids.end());
```
### Hashing
A `std::hash<>` specialization for `uuid` is provided in order to enable the use of `uuid`s in associative unordered containers such as `std::unordered_set`.
@ -427,6 +440,8 @@ namespace std {
constexpr const_iterator end() const noexcept;
constexpr std::span<std::byte const, 16> as_bytes() const;
constexpr std::strong_equality operator<=>(uuid const&) const noexcept = default;
template <typename TChar>
static uuid from_string(TChar const * const str, size_t const size);
@ -434,9 +449,6 @@ namespace std {
static uuid from_string(std::wstring_view str);
private:
friend constexpr bool operator==(uuid const & lhs, uuid const & rhs) noexcept;
friend constexpr bool operator<(uuid const & lhs, uuid const & rhs) noexcept;
template <class Elem, class Traits>
friend std::basic_ostream<Elem, Traits> & operator<<(std::basic_ostream<Elem, Traits> &s, uuid const & id);
};
@ -447,10 +459,6 @@ namespace std {
```cpp
namespace std {
inline constexpr bool operator== (uuid const& lhs, uuid const& rhs) noexcept;
inline constexpr bool operator!= (uuid const& lhs, uuid const& rhs) noexcept;
inline constexpr bool operator< (uuid const& lhs, uuid const& rhs) noexcept;
inline constexpr void swap(uuid & lhs, uuid & rhs);
template <class Elem, class Traits>
@ -516,7 +524,78 @@ namespace std {
}
```
## VI. UUID format specification
## VI. Alternative ordering design
The comparison of UUIDs is not an operation that makes logical sense but it is required for using UUIDs as keys for containers such as `std::set` or `std::map`. The technical specification of the `uuid` class given in the previous section features the three-way operator `<=>` as member of the class. The C++ community, however, is divided on this particular aspect (with long discussions happening in the [ISO C++ proposals forum](https://groups.google.com/a/isocpp.org/forum/#!forum/std-proposals)) between those that want convenience and those that want rigour. Although the RFC 4122 document, as quoted in the next section, does specify rules for lexical equivalence, not everybody is happy with the definition of comparison operator `<`.
The alternative put forward is to define non-member ordering functors. In this case, the library can define the following functors:
* `uuid_lexicographical_order` performs a lexicographic comparison (can provide compatibility with other systems)
* `uuid_fast_order` performs a memberwise comparison for efficiency.
These could be used as the comparison function for containers such as `std::set` or `std::map`:
```cpp
std::map<std::uuid, Value, std::uuid_fast_order> map;
```
```cpp
template<typename Value, typename Allocator = std::allocator<T>>
using uuid_map = std::map<std::uuid, Value, std::uuid_lexicographical_order, Allocator>;
```
In this case, the `uuid` class should be defined as follows:
```cpp
namespace std {
struct uuid
{
using value_type = uint8_t;
using const_iterator = /*implementation-defined*/;
constexpr uuid() noexcept = default;
constexpr explicit uuid(std::span<value_type, 16> bytes);
template<typename ForwardIterator>
constexpr explicit uuid(ForwardIterator first, ForwardIterator last);
constexpr uuid_variant variant() const noexcept;
constexpr uuid_version version() const noexcept;
constexpr std::size_t size() const noexcept;
constexpr bool is_nil() const noexcept;
constexpr void swap(uuid & other) noexcept;
constexpr const_iterator begin() const noexcept;
constexpr const_iterator end() const noexcept;
constexpr std::span<std::byte const, 16> as_bytes() const;
template <typename TChar>
static uuid from_string(TChar const * const str, size_t const size);
static uuid from_string(std::string_view str);
static uuid from_string(std::wstring_view str);
private:
template <class Elem, class Traits>
friend std::basic_ostream<Elem, Traits> & operator<<(std::basic_ostream<Elem, Traits> &s, uuid const & id);
};
}
```
In addition, the following must be added to the `<uuid>` header:
```cpp
namespace std {
struct uuid_lexicographical_order {
constexpr bool operator()(uuid const&, uuid const&) const;
};
struct uuid_fast_order {
constexpr bool operator()(uuid const&, uuid const&) const;
};
}
```
Should users require other types of comparison they can provide their own implementation and use them instead of the standard ones.
## VII. UUID format specification
The content of this section is copied from the RFC 4122 document that describes the UUID standard.
The UUID format is 16 octets; some bits of the eight octet variant field determine the layout of the UUID. That is, the interpretation of all other bits in the UUID depends on the setting of the bits in the variant field. The variant field consists of a variable number of the most significant bits of octet 8 of the UUID. The following table lists the contents of the variant field, where the letter "x" indicates a "don't-care" value.
@ -569,7 +648,14 @@ The version number is in the most significant 4 bits of the timestamp (bits 4 th
| 0 | 1 | 0 | 0 | 4 | The randomly or pseudo-randomly generated version specified in this document. |
| 0 | 1 | 0 | 1 | 5 | The name-based version specified in this document that uses SHA-1 hashing. |
## VII. References
### Rules for Lexical Equivalence
Consider each field of the UUID to be an unsigned integer as shown in the table in the section above. Then, to compare a pair of UUIDs, arithmetically compare the corresponding fields from each UUID in order of significance and according to their data type. Two UUIDs are equal if and only if all the corresponding fields are equal.
As an implementation note, equality comparison can be performed on many systems by doing the appropriate byte-order canonicalization, and then treating the two UUIDs as 128-bit unsigned integers.
UUIDs, as defined in this document, can also be ordered lexicographically. For a pair of UUIDs, the first one follows the second if the most significant field in which the UUIDs differ is greater for the first UUID. The second precedes the first if the most significant field in which the UUIDs differ is greater for the second UUID.
## VIII. References
* [1] Universally unique identifier, https://en.wikipedia.org/wiki/Universally_unique_identifier
* [2] A Universally Unique IDentifier (UUID) URN Namespace, https://tools.ietf.org/html/rfc4122