Pragmatic Regular structs
How to implement Regular data structures, using the spaceship operator, before better capabilities are added to C++
This article is part of a series on the history of regular data type in C++. It also revisits the old way of achieving the same functionality.
Introduction
Say you define a custom data structure, person
and want to ensure it’s
regular: just data, copyable, movable, strictly totally ordered and all that.
Here is the recipe using the spaceship operator for comparisons that the
compiler does not automatically generate yet.
1
2
3
4
5
6
7
8
struct person {
std::string first_name;
std::string last_name;
int age{};
constexpr std::strong_ordering
operator<=>(const person &) const noexcept = default;
};
default operator <=>
Well, all the other aspects are easy to understand, the compiler will generate
default constructor, destructor, copy, move, but not comparison. For that we
currently have to still provide a magic incantation. This will generate all the
comparisons though: the predicates ==
, !=
, <
, >
, <=
, >=
and the three
way <=>
.
const member
We’ve added operator<=>(const person & other) const
to reduce the amount of typing
compared with friend operator<=>(const person & x, const person & y)
. The
member version still takes two arguments: one via hidden this
, the other via
the other
parameter. Because we default it, we don’t even need to name the
other
parameter.
constexpr
The naive explanation is that constexpr
means that it might be executed at
compile time. That is wrong. The compiler can figure out what can be executed
at compile time and execute it at compile time. The correct explanation is that
here constexpr
is a promise that we’re not going to change the implementation
to do things that can’t be executed at compile time.
To be able to use it here requires that all the members have <=>
defined
constexpr
. That is currently the case for std::string
used above, but not
for e.g. std::set
, so weather you can use constexpr
here depends on the
types you’re using and the quality of the standard library you’re using.
Practically, you might care about this e.g. to check that a static array of
person
is sorted at compile time, or you might not, but the lack of
consistence is annoying.
strong_ordering
How do we ensure that of all the comparisons we get the proper strong version:
strictly totally ordered where <
is really less than and ==
is really
equality?
We return std::string_ordering
. This protects e.g. against loosing the
strength of the comparison e.g. if the type of age
is changed from int
to
float
. Unfortunately you might get an error when you try to use <
and the
defaulted operator <
is actually generated, rather than when you just
declared the class person
.