Keywords for override control

Date: 2010-10-07
Version: N3151=10-0141
Authors: Ville Voutilainen <ville.voutilainen@gmail.com>

Abstract

Multiple National Bodies requested in their FCD ballot responses that the virtual override control attributes are turned into keywords. The related NB comments are US 44 and CA 3. This paper proposes an analysis of whether to strive for using context-sensitive keywords or normal keywords (note that the CWG straw poll consensus was towards normal keywords). Note that this paper doesn't consider alignment facilities, but focuses on virtual override facilities. The solution proposed in this paper is using normal keywords as opposed to context-sensitive ones, and putting such keywords at the end of a declaration.

Background

Currently the virtual override control attribute example looks as follows:

class B {
   virtual void some_func();

   virtual void f(int);
   virtual void h(int);
   void j(int);
   void k();
   typedef B self;
};

class D [[base_check]] : public B {
   void sone_func [[override]] ();          // error: mis-spelled name

   void f [[override]] (int);               // ok: f implicitly virtual, overrides B::f
   virtual void f [[override]] (long);      // error: non-matching argument type
   virtual void f [[override]] (int) const; // error: non-matching cv-qualification
   virtual int  f [[override]] (int);       // error: non-matching return type

   virtual void g(long);      // ok: new virtual function introduced

   void h(int);               // error: h implicitly virtual, but overriding without marker
   virtual void h(double);    // error: hides B::h without marker
   virtual void h [[hiding]] (char *);      // ok

   using B::j;
   int j(double);                   // ok: not hiding due to "using"
   void j(int);                     // ok, despite 'obscuring' B::j(int)
   virtual int j [[hiding]] (void); // error: not hiding due to "using"

   int k;                           // error: hides B::k without marker

   int m [[hiding]] ( int );        // error: no hiding despite marker
   typedef D self;                  // error: hides B::self without marker
};

As a response to the CD ballot, NB comments US 41 and FI 1 requested for a facility to be able to mark intended overrides and have the implementation to check whether functions so marked are actually overrides. As a solution, the paper N2928 was produced, and the solution proposed in that paper was ultimatedly adopted and is currently part of the FCD.

Several experts have lamented the solution, saying that it uses attributes for semantic effects and that the language shouldn't use such semantic effects itself as that sets a bad example. Furthermore many people consider the attributes ugly. As a response to the FCD ballot, NB comments US 44 and CA 3 requested that the virtual override control attributes be changed to keywords. The CWG had a discussion on these NB comments, and the straw poll results were as follows:

  • Straw poll: override control (including final) as real keywords: 6 SF, 10 WF, 5 WA, 0 SA
  • Straw poll: override control (including final) as contextual keywords: 6 SF, 7 WF, 2 WA, 5 SA
  • Straw poll: override control (including final) as attributes: 1 SF, 6 WF, 3 WA, 10 SA

According to this straw poll, this paper analyzes both context-sensitive and normal keywords as possible solutions.

Languages like Java and C# have final (or in C#, sealed) as a keyword. C# additionally has override, whereas Java uses an annotation for override. C++/CLI has both sealed and override as keywords, but chooses to place such keywords at the end of a function declaration rather than at the beginning. Examples of these follow.

Java example:

public class base
{
    public final void f() {/*...*/}
    public void g() {/*...*/}
}

public class derived extends base
{
    // can't override f()
    @Override
    public void g() {/*...*/}
}

C# example:

public class base
{
    public sealed void f() {/*...*/}
    public virtual void g() {/*...*/}
}

public class derived : base
{
    // can't override f()
    public override void g() {/*...*/}
}

C++/CLI example:

public ref class base
{
public:
    virtual void f() sealed {/*...*/}
    virtual void g() {/*...*/}
};

public ref class derived : public base
{
public:
    // can't override f()
    void g() override {/*...*/}
};

Solution analysis with context-sensitive keywords

The main benefit of having context-sensitive keywords would be that they can appear only in certain contexts, and thus pose a smaller risk of breaking existing code.

While Java and C# place the keywords in positions different than C++/CLI, it can be argued that C++/CLI is at least an example of implementation experience for such keywords as context-sensitive keywords. They have been shipped in products for several years, and developer feedback based on field experience is very positive.

The example with such keywords would look somewhat similar to C++/CLI:

class base
{
public:
    virtual void f() final_overrider {/*...*/}
    virtual void g() {/*...*/}
};

class derived : public base
{
public:
    // can't override f()
    void g() virtual_override {/*...*/}
};

It is preferrable to put such virtual control keywords at the end of the declaration so that they don't clash with eg. return types at the beginning of declarations.

Daveed Vandevoorde pointed out the following problem with context-sensitive keyword, or specifically the facility to be able to mark cases where names are hidden:

struct Z {};
struct X
{
  // what does this mean?
  // is it a struct declaration
  // or a variable declaration?
  struct Z hides_name;
};

struct C {};
struct A
{
  typedef int C;
};

struct B : A
{
  // looks like we forward-declare a struct
  // that hides the name C? Or are we declaring
  // a variable?
  struct C hides_name;
};

// is this the solution?
struct B : A
{
  hides_name struct hides_name C p;
};

The gist of the example is that for the name-hiding facility, it's sometimes still difficult to know to which name the keyword appertains. Attributes don't suffer from such problems since wherever they were able to appear, they didn't clash with names in such a manner, and the rules for appertaining were clear.

Solution analysis with normal keywords

For context-insensitive, normal keywords, it's less important where the keywords are placed because the words are reserved. We could put them at the beginning of declarations or at the end.

During the discussion of attributes, Francis Glassborow pointed out that the beginning of declarations is becoming crowded. If we put the virtual control keywords at the beginning, we can end up with examples like the one below:

struct B
{
   virtual volatile const unsigned long int f()
      volatile const noexcept;
   void f(int g);
};

struct D : B
{
   virtual hides_name virtual_override final_overrider volatile const unsigned long int f()
      volatile const noexcept;
};

Putting the new keywords at the end at least alleviates the situation somewhat:

struct B
{
   virtual volatile const unsigned long int f()
      volatile const noexcept;
   void f(int g);
};

struct D : B
{
   virtual volatile const unsigned long int f()
      hides_name virtual_override final_overrider volatile const noexcept;
};

There are people who think these control keywords should be in the same place with virtual. As mentioned, that place is already crowded.

Painting the bikeshed

The initial proposal posted on the mailing list was as follows:

Attribute Keyword
[[override]] virtual_override
[[final]] final_overrider
[[hiding]] hiding_name
[[base_check]] verify_decls/check_name_decls

Daveed Vandevoorde suggested the following:

Attribute Keyword
[[override]] override_virtual
[[final]] final_virtual
[[hiding]] hiding_name
[[base_check]] explicit class/struct

For "override_virtual" and "final_virtual" he said that the current virtual keyword would not be needed in these cases but would rather be banned for these cases. He also suggested that the keywords be placed at the beginning of a declaration.

Bjarne Stroustrup suggested the following colors for the shed:

Attribute Keyword
[[override]] override/do_override
[[final]] no_override
[[hiding]] do_hide/hides
[[base_check]] base_check

Mike Spertus contributed the following hues:

Attribute Keyword
[[override]] override_method
[[final]] final_method
[[hiding]] hide_method
[[base_check]] base_check

He later contributed another proposal:

Attribute Keyword
[[override]] override_member
[[final]] final_member
[[hiding]] hide_member

Herb Sutter contributed the following palette:

Attribute Keyword
[[override]] override_it
[[final]] finalize_it
[[hiding]] hide_it

Discussion about the solutions

Regardless of whether we ultimately choose to use context-sensitive or normal keywords, it would be prudent to choose keywords that aren't used in existing code, so as to not confuse the user. For any normal keywords, we must pick ones that are not widely used.

Due to the problem depicted by Daveed, and due to the guidance given by straw polls, it seems like normal keywords as opposed to context-sensitive ones are the path forward.

Proposal

Use normal keywords, as they don't suffer from the problem depicted by Daveed, and place the keywords at the end of a declaration. Thus far we have just done Google Code Searches, and while it's a huge question of opinion which keywords to choose, here is an initial proposal:

Attribute Keyword
[[override]] ovrdecl
[[final]] finaldecl
[[hiding]] hidedecl
[[base_check]] strictdecl

With this proposal, the attribute example in FCD would look like this:

class B {
   virtual void some_func();

   virtual void f(int);
   virtual void h(int);
   void j(int);
   void k();
   typedef B self;
};

class D strictdecl : public B {
   void sone_func() ovrdecl;          // error: mis-spelled name

   void f(int) ovrdecl;               // ok: f implicitly virtual, overrides B::f
   virtual void f(long) ovrdecl;      // error: non-matching argument type
   virtual void f(int) ovrdecl const; // error: non-matching cv-qualification
   virtual int  f(int) ovrdecl;       // error: non-matching return type

   virtual void g(long);      // ok: new virtual function introduced

   void h(int);               // error: h implicitly virtual, but overriding without marker
   virtual void h(double);    // error: hides B::h without marker
   virtual void h(char *) hidedecl;      // ok

   using B::j;
   int j(double);                   // ok: not hiding due to "using"
   void j(int);                     // ok, despite 'obscuring' B::j(int)
   virtual int j(void) hidedecl; // error: not hiding due to "using"

   int k;                           // error: hides B::k without marker

   int m(int) hidedecl;        // error: no hiding despite marker
   typedef D self;                  // error: hides B::self without marker
};