Alex Stepanov defined Regular Types as types satisfying certain properties around copying and equality. Now that C++11 has added move semantics to the realm of generic progr
Constraints of generic programming are best stated in terms of expressions. A more modern rendition of the same constraint on copiability would be that both statements should be valid:
T b = a;
and
T b = ra;
where a
is an lvalue with type T
or const T
and ra
is an rvalue with type T
or const T
. (With similar post-conditions.)
This formulation is in the spirit of the paper, I believe. Do note that C++03 already makes use of notions like lvalues and rvalues, such that the constraint we've expressed requires that something like T source(); T b = source();
be valid -- certainly something that seems sensible.
Under those constraints, then not much changes with C++11. Of particular note is that such a (pathological) type is irregular:
struct irregular {
irregular() = default;
irregular(irregular const&) = default;
irregular& operator=(irregular const&) = default;
irregular(irregular&&) = delete;
irregular& operator=(irregular&&) = delete;
};
because something like irregular a; irregular b = a;
is valid while irregular source(); irregular b = source();
isn't. It's a type that is somewhat copyable (resp. copy assignable), but not quite enough. [ This has been considered somewhat of a defect and is slated to be changed for C++1y, where such a type will in fact be copyable. ]
Going further, for the post-condition that a copy must be equivalent in some sense to the original (or, for rvalues, to the original before the copy) to hold, a move special member can only ever be an 'optimization' of the respective copy special member. Another way to put it is that copy semantics are a refinement of move semantics. This means that the assertion must hold in the following:
T a;
T b = a;
T c = std::move(a);
assert( b == c );
I.e. whether we arrived there via a copy 'request' (that is, an expression involving an lvalue source) or via a move request (an expression involving an rvalue source), we must have the same result regardless of what 'actually' happened (whether a copy special member or move special member was involved, if at all).
Of interest is the fact that the traits such as std::is_copy_constructible
used to be called std::has_copy_constructor
, but were renamed to put the emphasis on expressions rather than intrinsic properties: something like std::is_copy_constructible
is true regardless of the fact that int
has no constructors or assignment operators.
I advise you to really do generic programming by expressing constraints on the expression level because e.g. the presence or absence of a move constructor is neither sufficient nor necessary for a type to be copy constructible.