Document number: n2123=06-0193

Alisdair Meredith
2006-10-22

Adding the prohibited access specifier to C++09

Motivation:

There is a common idiom in C++03 to declare members private, but not define them, to prevent access to that name. Two common examples are 'hiding' the copy constructor/assignment operator, or removing conversions from overload sets.

The modules proposal would make changes that hide private members, rather than simply render them inaccessible. This is a breaking change, and the prohibited access specifier is suggested as an intrusive change to retain the old behaviour.

While modules are now moving into a separate TR, the idea of the new access specifier is appealing. * Code written with this specifier is much clearer in intent * Prohibited members are not accessible to friends, or the class itself As the feature is small and self-contained, it is proposed to extract it from the modules proposal and implement immediately in C++09. This has the added appeal of introducing the one new keyword required for the modules TR into the grammar as part of the standard itself.

Examples:

Example 1 : Inhibit copying
The common case of the object that should not be copied, but the compiler implicitly declares copy operations.


template< typename T >
struct owned_ptr {
public:
  explicit owned_ptr( T * p ) : pt( p ) {}
  ~owned_ptr() { delete pt; }

  T * operator->() { return pt; }
  T const * operator->() const { return pt; }

private:
  T * pt;

prohibited:
  owned_ptr( owned_ptr const & );
  owned_ptr & operator=( owned_ptr const & );
};

The current idiom is to declare the copy operations as private, and choose not to implement them as they are never called. While this technique works, it is far from obvious - especially when trying to explain to someone new to the language why they would want to declare a function they never plan to define.

While this new specifier still requires declaring the functions, at least it is clear that it is not permitted to define them, and the compiler will enforce this for you. It is much simpler to teach to the casual programmer, who is wondering how they are supposed to know which private functions have definitions, and which are just for show.

This is further strengthened when the casual programmer comes back and points out that the class itself (by current convention) can still make copies, and likewise any friends. These errors go straight past the translator and are only caught at link time, which is not the easiest time for causal programmers to be diagnosing errors. Again, the use of prohibited as an access specifier takes these questions away, and the code clearly states its intent.

Example 2 : Prevent Narrowing conversions
Declare overloads of public members to prevent narrowing conversions


struct X {
public:
  void foo( float );

prohibited:
  void foo( double );
};

Again, the prohibited access specifier more clearly expresses the intent than an unimplemented private function. This example seems more likely to benefit from the diagnostics when calling the double overload from other members of friends.

Unusual cases:

A prohibited base renders a class unusable, as the destructor cannot be implemented. Therefore, prohibited base classes should be ill-formed.

Likewise, a prohibited data member suffers the same problem unless maybe it is a fundamental type. Rather than add a series of contorted exceptions, prohibited data members will be ill-formed.

Prohibited virtual functions offer their own set of problems. It would be ill-formed to call such a function, and ill-formed to override it. Likewise, declaring a virtual function override as prohibited would be a severe problem when called through pointer-to-base. Therefore all use of virtual keyword with prohibited access specifier will be ill-formed.

It is not clear what a static prohibited member function would mean, so for simplicity this too is deemed ill-formed.

History:

The prohibited access specifier was first suggested by Daveed Vandevoorde in the modules proposal, n2006 and n2073. The problem with implicitly declared member functions has a long history and the well-known workaround features prominently on the list of "C++ Embarassments"

Desciption:

The proposal adds a new access specifier to the grammar, prohibited. A program is ill-formed if it attempts to use any member that has been declared prohibited.

The case of a prohibited base class is automatically ill-formed, as the derived class would need access to the base class destructor.

It is not clear what it would mean to declare data members, member classes, enums, typedefs or member (class) templates prohibited, so a program is ill-formed if a non-empty prohibited section contains anything but function declarations.

As prohibited members cannot be referred to, it will be ill-formed to try to define them, even as inline functions.

It is not clear what it should mean to overide a virtual function in a prohibited section. However, as there is no definition for the overide, the program will not link. Therefore, it is suggested that this be ill-formed and diagnosed at translation time (final wording to be supplied)

Changes to the Working Paper:

Clause 10:
Augment the grammar for access-specifier with

prohibited

Add to Clause 11 p 1: [class.access]

prohibited: that is, the name cannot be used.

[Note: a program is ill-formed if it tries to provide a definition for a prohibited member]

Add paragraph 6 to clause 11.1 [class.access.spec]

The member-specification for a prohibited access-specifier may only contain non-static, non-virtual function declarations, or shall be empty.

Append to 11.2p1 [class.access.base]

A program is ill-formed if a class is declared to be a base class for another class using the prohibited access specifier.

Notes:
Nothing to say about friends (11.4), existing wording already covers the case.
Nothing to say about using declarations (7.3.3) - they are ill-formed by 11p1.
Nothing to say about access declarations (11.3) as they are defined in terms of using declarations.