Document Number |
P1196R0 |
Date |
2018-09-27 |
Project |
Programming Language C++ |
Audience |
Library Evolution Working Group |
Summary |
This paper proposes that |
Summary
This paper proposes the addition of an optional 64 bit identifier to error_category
, such that two
instances of the same category may compare equal. When a category does not have an identifier, the
comparison reverts to address-based, retaining backward compatibility.
Motivation
In the presence of dynamic libraries without global symbol visibility, it’s hard to guarantee that category objects are unique. Implementations are already known to use a technique similar to the one suggested here to make copies of the standard generic and system categories compare equal even when their addresses do not match. This is currently non-conforming, but it’s more useful than the behavior the standard mandates.
In addition, relying on address-based comparisons makes it impossible to declare category objects
constexpr
and makes it impossible to declare operator<
constexpr
.
Switching to unique category identifiers solves both problems.
Design Rationale
The choice of 64 bits for the identifier is motivated by the desire to make it possible for programmers to define their own categories without a central authority allocating identifiers. Collisions between 64 bit random numbers are rare enough.
The identifiers of the standard generic and system categories have been specified to help portability and reproducibility.
error_category::operator==
compares rhs.id_
to 0, instead of this->id_
, under the assumption that
in category comparisons, it’s more likely for the right hand side to be a known constant, allowing for the
check to be performed at compile time.
error_code::operator==
and error_condition::operator==
are changed to compare the values first, as
comparing the values is cheaper than comparing the categories (requiring an identifier to be fetched through
the category pointer.)
Implementability
The proposed changes have been implemented in Boost.System and are presently on its develop branch. If all goes well, they will ship in Boost 1.69.
One difference between Boost.System and the proposed text is that the destructor of boost::system::error_category
has been made protected and nonvirtual, enabling category types to be literal and consequently enabling constexpr
category objects. This is expected to be unnecessary for C++20 if P1077R0 (or an equivalent)
is accepted.
Proposed Changes
-
// 18.5.5, comparison functions constexpr bool operator==(const error_code& lhs, const error_code& rhs) noexcept; constexpr bool operator==(const error_code& lhs, const error_condition& rhs) noexcept; constexpr bool operator==(const error_condition& lhs, const error_code& rhs) noexcept; constexpr bool operator==(const error_condition& lhs, const error_condition& rhs) noexcept; constexpr bool operator!=(const error_code& lhs, const error_code& rhs) noexcept; constexpr bool operator!=(const error_code& lhs, const error_condition& rhs) noexcept; constexpr bool operator!=(const error_condition& lhs, const error_code& rhs) noexcept; constexpr bool operator!=(const error_condition& lhs, const error_condition& rhs) noexcept; constexpr bool operator< (const error_code& lhs, const error_code& rhs) noexcept; constexpr bool operator< (const error_condition& lhs, const error_condition& rhs) noexcept;
-
namespace std { class error_category { private: uint64_t id_ = 0; // exposition only protected: explicit constexpr error_category(uint64_t id) noexcept; public: constexpr error_category() noexcept = default(); virtual ~error_category() = default(); error_category& operator=(const error_category&) = delete; virtual const char* name() const noexcept = 0; constexpr virtual error_condition default_error_condition(int ev) const noexcept; constexpr virtual bool equivalent(int code, const error_condition& condition) const noexcept; constexpr virtual bool equivalent(const error_code& code, int condition) const noexcept; virtual string message(int ev) const = 0; constexpr bool operator==(const error_category& rhs) const noexcept; constexpr bool operator!=(const error_category& rhs) const noexcept; constexpr bool operator< (const error_category& rhs) const noexcept; }; constexpr const error_category& generic_category() noexcept; constexpr const error_category& system_category() noexcept; }
-
constexpr error_category() noexcept;
Effects: Constructs an object of class error_category.
explicit constexpr error_category(uint64_t id) noexcept;
Effects: Initializes
id_
toid
.constexpr bool operator==(const error_category& rhs) const noexcept;
Returns: this == &rhs
rhs.id_ == 0? this == &rhs: id_ == rhs.id_
.constexpr bool operator<(const error_category& rhs) const noexcept;
Returns: less<const error_category*>()(this, &rhs)
As if:if(id_ < rhs.id_) { return true; } if(id_ > rhs.id_) { return false; } if(rhs.id_ != 0) { return false; // equal } return less<const error_category*>()(this, &rhs);
-
Remarks: The object’s
default_error_condition
and equivalent virtual functions shall behave as specified for the classerror_category
. The object’sname
virtual function shall return a pointer to the string"generic"
. The object’serror_category::id_
subobject shall be0xB2AB117A257EDF0D
.Remarks: The object’s equivalent virtual functions shall behave as specified for class
error_category
. The object’sname
virtual function shall return a pointer to the string"system"
. The object’serror_category::id_
subobject shall be0x8FAFD21E25C5E09B
. The object’sdefault_error_condition
virtual function shall behave as follows:
-
constexpr bool operator==(const error_code& lhs, const error_code& rhs) noexcept;
Returns: lhs.category() == rhs.category() && lhs.value() == rhs.value()
lhs.value() == rhs.value() && lhs.category() == rhs.category()
.constexpr bool operator==(const error_condition& lhs, const error_condition& rhs) noexcept;
Returns: lhs.category() == rhs.category() && lhs.value() == rhs.value()
lhs.value() == rhs.value() && lhs.category() == rhs.category()
.constexpr bool operator<(const error_code& lhs, const error_code& rhs) noexcept;
constexpr bool operator<(const error_condition& lhs, const error_condition& rhs) noexcept;
ABI Implications
The proposed addition of a 64 bit data member to error_category
unfortunately constitutes an ABI break.
One possible way to implement it is by adding a magic signature in front of the new id_
member:
class error_category
{
private:
uint32_t magic_ = 0xbe5da313;
uint32_t version_ = 1;
uint64_t id_ = 0;
// ...
and then, before accessing id_
, check whether magic_
has the value of 0xbe5da313
and whether version_
is positive and smaller than some sufficient upper limit.
This has the additional benefit of enabling further error_category
ABI evolution, realized by increasing
version_
on each ABI change.
-- end