C.21 Rule of 5/6
Here is a counter argument to C.21: http://howardhinnant.github.io/classdecl.html
Imho there is such a thing as too much verbosity. I think it is better to learn when the compiler supplies, deletes or inhibits special member functions. Then ensure that all 6 of your member functions are correct (preferably with testing each one, even deleted ones). But not necessarily depend on user-declarations for all 6.
For example:
class day
{
unsigned char d_;
public:
day() = default;
explicit constexpr day(unsigned d) noexcept;
// ...
is far more readable than:
class day
{
unsigned char d_;
public:
~day() = default;
day() = default;
day(day const&) = default;
day& operator=(day const&) = default;
day(day&&) = default;
day& operator=(day&&) = default;
explicit constexpr day(unsigned d) noexcept;
// ...
And regardless one should have a unit test for this class that includes:
static_assert(std::is_trivially_destructible<day>{});
static_assert(std::is_trivially_default_constructible<day>{});
static_assert(std::is_trivially_copy_constructible<day>{});
static_assert(std::is_trivially_copy_assignable<day>{});
static_assert(std::is_trivially_move_constructible<day>{});
static_assert(std::is_trivially_move_assignable<day>{});
// ...
This test, more than the class declaration, ensures that class day has the desired design.
And the ordering of special members that are user-declared (as suggested in the linked article above), makes it easy for the reader to see which special members are intentionally compiler-declared.
I notice that in your example you've defaulted the default constructor and in C.21 it is explicitly noted Declaring any special member function except a default constructor, even as =default or =delete will... . It doesn't, however, explicitly treat this as an exception though it later states This is known as “the rule of five” or “the rule of six”, depending on whether you count the default constructor.. Implying the default constructor may be considered an exception to the rule. Should it be? If so, this might be an unfortunate choice of example and maybe C.21 could be clearer about it being an exception.
I assume everyone's in agreement that generally if you're implementing a user defined destructor then you usually don't want a defaulted copy constructor. If that's the case then using =default explicitly would make this an explicit decision. Under your proposed convention that communication is missing. I as the reader must assume that the author considered the behavior of the implicitly declared copy constructor and decided it was acceptable as opposed to simply overlooking it entirely. In this respect readability is reduced. Is this the right trade off?
You do point out that your ordering makes it easier to see which members are intentionally compiler declared. I agree this is easier (though it's still not explicit) but this seems like a difficult convention to adopt for existing code and I suspect (much like many conventions) you'd only really see the benefit if all (or enough for exceptions to stand out) of the code base were following it.
I'm intrigued by the assertion that classes should have unit tests for the existence/triviality of their special members. I can see that this would be useful when writing a library where you have no control over or visibility of how your class is used. Would you apply this rule equally to a class which exists within a code base with no dependents? It seems to me that if a modification to my class makes it non-copyable and I can prove no one is copying it currently then that's just fine.
This has spawned a lively discussion on reddit:
https://www.reddit.com/r/cpp/comments/f918oz/how_i_declare_my_class_and_why_howard_e_hinnant/
I agree that the "red squares" in the table should be avoided. I should probably add that to my article. And by this I mean that if you declare the destructor, don't depend on the deprecated behavior of a compiler-declared copy member.
On the unit tests, my preference is to static_assert on all 6 special members. However I fully understand that adopting such practice 100% in a code base that is maintained by more than just a few people is a daunting task that may never be actually achieved. However, if it is a goal, then it is more likely to be striven for, than if it is not.
I've already received feedback from people that they had never thought about testing their special members this way, and they were going to start. So that alone made the article worth it.
As for how the article applies to this guideline, I'll leave it up to others to decide. Herb asked me to generate this issue and point to the article, and I was happy to do so.