Summary:
For C++11 I would include:
- move-ctor (
noexcept
)
- move-assign (
noexcept
)
- total ordering (
operator<()
for natural total order and std::less<>
if a natural
total order does not exist).
hash<>
And would remove:
swap()
(non-throwing) - replaced by move operations.
Commentary
Alex revisits the concept of a regular type in Elements of Programming. In fact, much of the book is devoted to regular types.
There is a set of procedures whose inclusion in the computational
basis of a type lets us place objects in data structures and use
algorithms to copy objects from one data structure to another. We call
types having such a basis regular, since their use guarantees
regularity of behavior and, therefore, interoperability. -- Section 1.5 of EoP
In EoP, Alex introduces the notion of an underlying_type which gives us a non-throwing swap algorithm that can be used to move. An underlying_type template isn't implementable in C++ in any particularly useful manner, but you can use non-throwing (noexcept
) move-ctor and move-assign as reasonable approximations (an underlying type allows moving to/from a temporary without an additional destruction for the temporary). In C++03, providing a non-throwing swap()
was the recommended way to approximate a move operation, if you provide move-ctor and move-assign then the default std::swap()
will suffice (though you could still implement a more efficient one).
[ I'm on record as recommending that you use a single assignment operator, passing by value, to cover both move-assign and copy-assign. Unfortunately the current language rules for when a type gets a default move-ctor causes this to break with composite types. Until that is fixed in the language you will need to write two assignment operators. However, you can still use pass by value for other sink arguments to avoid combinatorics in handling move/copy for all arguments. ]
Alex also adds the requirement of total ordering (though there may not be a natural total order and the ordering may be purely representational). operator<()
should be reserved for the natural total ordering. My suggestion is to specialize std::less<>()
if a natural total ordering is not available, there is some precedent for that in the standard).
In EoP, Alex relaxes the requirements on equality to allow for representational-equality as being sufficient. A useful refinement.
A regular type should also be equationally complete (that is, operator==()
should be implementable as a non-friend, non-member, function). A type that is equationally complete is also serializable (though without a canonical serialization format, implementing the stream operators are of little use except for debugging). A type that is equationally complete can also be hashed. In C++11 (or with TR1) you should provide a specialization of std::hash
.
Another property of regular types is area()
for which there is not yet any standard syntax - and likely little reason to actually implement except for testing. It is a useful concept for specifying complexity - and I frequently implement it (or an approximation) for testing complexity. For example, we define the complexity of copy as bounded by the time to copy the area of the object.
The concept of a regular type is not language-specific. One of the first things I do when presented with a new language is work out how regular types manifest in that language.