Doc. No.: N4401
Date: 2015-04-07
Project: Programming Language C++, Evolution Working Group
Reply To: Michael Price
<michael.b.price.dev@gmail.com>

Defaulted comparison operator semantics should be uniform

I. Introduction

Argues that WG21 should keep the semantics of any compiler-generated comparison operators uniform with those of other compiler-generated special member functions; specifically that mutable and pointer members should be included in the default comparison operations.

II. The Details

Given the following types and their relationships

  struct Base {
      std::string name;
  };

  class Member {
      int value;
  };

  class MyType : public Base {
      mutable Member member;
      std::string * id;
  };
  

We have existing rules that determine what happens during the constructor of an instance of MyType.

Paragraph 12.6.2.13 [1] reads:

In a non-delegating constructor, initialization proceeds in the following order:

—First, and only for the constructor of the most derived class (1.8), virtual base classes are initialized in the order they appear on a depth-first left-to-right traversal of the directed acyclic graph of base classes, where "left-to-right" is the order of appearance of the base classes in the derived class base-specifier-list,

—Then, direct base classes are initialized in declaration order as they appear in the base-specifier-list (regardless of the order of the mem-initializers).

—Then, non-static data members are initialized in the order they were declared in the class definition (again regardless of the order of the mem-initializers).

—Finally, the compound-statement of the constructor body is executed.

[Note: The declaration order is mandated to ensure that the base and member subobjects are destroyed in the reverse order of initialization. —end note]

The ordering rules for implictly defined copy and move constructors (12.8.15) reference paragraph 12.6.2, and the ordering for implicity defined copy and move assignment operators (12.8.28) are similarly described.

Paragraph 12.8.15 [1] reads:

The implicitly-defined copy/move constructor for a non-union class X performs a memberwise copy/move of its bases and members. [Note: brace-or-equal-initializers of non-static dta members are ignored. See also the example in 12.6.2. —end note] The order of initialization is the same as the order of initialization of bases and members in a user-defined constructor (see 12.6.2). Let x be either the parameter of the constructor or, for the move constructor, an xvalue referring to the parameter. Each base or non-static data member is copied/moved in the manner appropriate to its type:

—if the member is an array, each element is direct-initialized with the corresponding subobject of x;

—if a member m has rvalue reference type T&&, it is direct-initialized with static_cast<T&&>(x.m);

—otherwise, the base or member is direct-initialized with the corresponding base or member x.

Virtual base class subobjects shall be initialized only once by the implicitly-defined copymove constructor (see 12.6.2).

Paragraph 12.8.28 [1] reads:

The implicitly-defined copy/move assignment operator for a non-union class X performs memberwise copy/move assignment of its subobjects. The direct base classes of X are assigned first, in the order of their declaration in the base-specifier-list, and then the immediate non-static data members of X are assigned, in the order in which they were declared in the class definition. Let x be either the parameter of the function or, for the move operator, an xvalue referring to the parameter. Each subobject is asigne din the manner appropriate to its type:

—if the subobject is of class type, as if by a call to operator= with the subobject as the object expression and the corresponding subobject of x as a single function argument (as if by explicity qualification; that is, ignoring any possible virtual overriding functions in more derived classes);

—if the subobject is an array, each element is assigned, in the manner appropriate to the element type;

—if the subobject is of scalar type, the built-in assignment operator is used.

It is unspecified whether subobjects representing virtual base classes are assigned more than once by the implicitly-defined copy/move assignment operator.

Tellingly, we have no special considerations for members that could possibly cause undesired behavior, particularly for mutable and pointer members. We've left it to the designer of MyType to understand whether the generated special member functions satisfy the requirements for the type, and if they do not, they are free to implement the desired semantics.

In N4175 [2], it was argued that mutable and pointer members should be excluded from default-generated comparison operators. The general argument is that any generated comparison operators for those kinds of members might not be what a user expects; ordered comparisons for pointers currently have serious problems and comparing a mutable member could have side-effects on its value. Those concerns are valid, but that is our current state with regards to other generated special member functions. Therefore, the proposal to include those types in the default comparisons are likely to only be palatable if we agree to restrict the generation to those cases where we believe it should be a safe operation, or to impose stricter requirements, such as forcing the author to explicitly opt-in to the default behavior by requiring the author of a type to request the default implementation via = default;.

Complicating the special member functions by ignoring components of an instance's value for a subset of those functions doesn't seem like the best path forward to this author. Instead, we should not try to assume too much about the intention of the author of a class and trust that they understand that the use of "unusual" types as members requires doing extra work, as we have maintained for the past several decades.

III. Acknowledgements

Many thanks to my employer, Lexmark International, for continuing to support my work on the committee. Several of my co-workers, Andrew Regier, Chris Sammis, and Serhan Yengulalp, provided invaluable feedback on early drafts that led me to clarify my position and address previously ignored issues.

IV. References

[1] R. Smith, Working Draft, Standard for Programming Language C++. N4140.
[2] B. Stroustrup, Default Comparisons. N4175.