What is a “Regular Type” in the context of move semantics?

后端 未结 3 2138
轮回少年
轮回少年 2020-12-01 02:11

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

3条回答
  •  有刺的猬
    2020-12-01 02:46

    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::value && std::is_move_assignable::value 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.

提交回复
热议问题