Defaulting non-public special member functions on first declaration

Date: 2010-02-03
Version: N3023=10-0013
Authors: Ville Voutilainen <ville.voutilainen@gmail.com>

Abstract

The resolution of Core Issue 906 forbids defaulting a non-public special member function on its first declaration. I believe this resolution to be incorrect, and this document explains why. This document proposes that defaulting a non-public special member function on its first declaration is an important part of design vocabulary, and should not be considered as something that affects triviality. Defaulting should retain triviality, or retain the lack of triviality, and people shouldn't expect defaulting to bring forth triviality. Furthermore, non-public special member functions defaulted on first declaration should not be considered user-provided.

I wish to thank Daniel Kr├╝gler for performing a sanity check for this paper, and Lawrence Crowl for reviewing the paper and providing feedback and suggestions for improvement.

Background

N2346 states that "A special member function is user-provided if it is user-declared and not explicitly defaulted on its first declaration." Further, N3000 states in 12.8 Copying class objects [class.copy] p6 that "A copy constructor for class X is trivial if it is not user-provided". Therefore, I would assume that if it's allowed to default a non-public copy constructor on its first declaration, a non-public copy constructor could be trivial. For constructors, 12.1 Constructors [class.ctor] p5 states that "A default constructor is trivial if it is not user-provided". Therefore, I'd assume that if it's allowed to default a non-public constructor on its first declaration, a non-public default constructor could be trivial.

The current status quo is that in order to be defaulted on the first declaration, a special member function must be public. During the discussion of Core Issue 906 in Santa Cruz it was suggested by John Spicer that triviality should be made more explicit by forcing the user to do the defaulting after the first declaration, assuming that defaulting a non-public member function will remove triviality because such a function would be considered user-provided.

Jason Merrill and I were of the opinion that access should not be the deciding factor when allowing defaulting on the first declaration, but it should be decided on whether the defaulted special member function is considered user-provided. It seems to me that N2346 strongly suggests that a defaulted special member function should not be considered user-provided, and thus non-public member functions should not affect triviality if they are defaulted.

Jens Maurer thought in message 15490 that a base class with a protected copy constructor makes a derived class not trivially copyable, because it has a subobject that is not trivially copyable. Mike Miller asked for a use case, which was provided later in reflector discussions, in message 15492 on the core reflector.

The problem, cases that should be allowed and trivial

I think it's problematic that non-public special member functions can't be defaulted on their first declaration. The defaulting is something that people do when they want to retain triviality if possible. The access control is orthogonal to that, because access control in this case is a design tool. Forbidding defaulting on first declaration seems to restrict the design vocabulary considerably.

Furthermore, regarding the concern by Jens Maurer, a base class with a protected copy constructor is not CopyConstructible to begin with; a derived class can be, however, if the derived class has a public copy constructor. My take on that is that a derived class can be trivially copyable, even if the base class alone would not be, as long as the copy constructor of the base is accessible to the derived class, and as long as neither copy constructor is user-provided.

This applies to all subobjects, a class aggregating a subobject having a private copy constructor can still be trivially copyable if the aggregating class is the friend of the aggregated class.

I shall repeat the use cases provided on the mailing list. Let's first consider a case where a copy constructor is protected and defaulted:

struct B
{
protected:
   B(const B&) = default;
};

struct D : B
{
};

The use case for this defaulting would be that the user is attempting to forbid slicing copies from D to B, but still retaining triviality.

When the design evolves, the user decides that instances of the base are harmful, so she modifies the base thus:

struct B
{
protected:
    B() = default;
    B(const B&) = default;
};

There's still no reason for the user to assume that D wouldn't be trivially copyable, because there's no user-provided function in sight. D should also be trivially constructible, for the same reason.

For other subobjects besides base classes, let's consider the following:

struct part
{
friend class aggregate;
private:
    part() = default;
    part(const part&) = default;
};

struct aggregate
{
    part x;
};

I'd expect it to be perfectly reasonable for the user to assume aggregate to be trivially copyable (and trivially constructible).

Some cases that should be allowed but aren't trivial

Let's consider the following example:

struct base
{
    std::shared_ptr<int> member;
};

The implicitly declared copy constructor will not be trivial, since shared_ptr's copy constructor isn't. Now, if this is a base class that people don't want to have instances of, we may write

struct base
{
    std::shared_ptr<int> member;
protected:
    base() = default;
    base(const base&) = default;
};

This shouldn't affect the triviality in any way. Yet users expect it to be allowed. While destructors work just fine with an empty definition, copy constructors don't. Thus the brevity of being able to default on first declaration seems superior to the alternatives. Consider the following:

struct base
{
    std::shared_ptr<int> member;
protected:
    base(const base&) {} // doesn't work at all!
};

Another, correct attempt would be this:

struct base
{
    std::shared_ptr<int> member;
protected:
    base(const base&);
};

base::base(const base&) = default; // tedious to write outside the class definition

According to my surveys, users understand that defaulting such a copy constructor will not bring forth triviality. They also want to do the defaulting concisely, and they don't want to be forced to write it outside the class.

Discussion about the solution

Help is needed for drafting, because I don't know if it's sufficient to just strike the requirement for a special member function defaulted on its first declaration to be public.

I find it hard to agree with the intent of prohibiting defaulting non-public special member functions on first declaration. It seems to be a language mechanism that emphasizes cases where triviality is not achieved. However, it should be noted that an implicitly declared copy constructor is not ill-formed for classes that aren't trivially copyable, so for the sake of consistency, I think defaulted copy constructors should not be ill-formed in that case either, whether public or not. And as suggested by this paper, I don't think a protected or even private special member function should result in loss of triviality, and most importantly, such a function should definitely not be considered user-provided just because it has different access than the implicitly declared function would have.

I'm inclined to think it's a quality-of-implementation issue to diagnose the lack of triviality, whether in the case of implicitly declared special member functions or in the case of special member functions defaulted on first declaration. I consider forbidding defaulting non-public special member functions on first declaration to be overkill for what it's seemingly trying to achieve, and I consider it grossly incorrect from the design vocabulary point of view.