B
has a virtual function f
, and
the user declares their own f
in derived class
D
, thereby accidentally overriding B::f
.
B
has a virtual function f
, and
the user wishes to override that function in the derived class
D
, but misspells the name as D::foo
.
B
has a virtual function f
, and
the user wishes to override that function in the derived class
D
, but inadvertently alters the parameter list.
B1
has a virtual function f
, another base class
B2
also has a virtual function f
with the same
signature, the user derives a class D
from both
B1
and B2
and declares D::f
to
override both B1::f
and B2::f
, one of which may
have been inadvertent. (This is sometimes referred to as the Siamese Twins
problem.)
The scope of the problem was reduced to the minimal necessary to solve the common real-world problems. This means it does not tackle ideas such as 'final overriders' or function renaming, both of which would be available if a scheme closer to the C++/CLI standard was pursued.
N2108 was presented at the Portland meeting and introduced the concept of an explicit polymorphic class as a refinement of the existing polymorphic class type. The virtual function syntax wording could then be updated to use the new class type, while deferring the final choice of syntax to mark the class as explicit to a later paper. This resolved the viral problem by marking the derived class instead of the base, and that is more frequently under the developer's control. Feedback was to advance to Core once a suitable mark-up could be found.
Attributes for C++0x were initially proposed in N2236 and subsequently adopted in N2751. They provide a suitable markup mechanism without overloading existing keywords or introducing new ones.
N2365 was produced from feedback at the Toronto 2007 meeting, adopting the attribute syntax. The main change was a direct request to solve the similar problems with name-hiding. There remained some reservation that while any program marked up with the feature that successfully translated would have identical meaning (the attributes purely add additional checks and do not affect semantics) there were still some subtle changes in the definition of the virtual keyword.
N2852 resolved the outstanding issues in direct response to national body comments from the first CD ballot. The notion of an explicitly polymorphic class was no longer needed, and a new attribute was introduced to explicitly mark a function that should override, rather than add subtle extra meaning to the virtual keyword.
This paper is a response to the Core Working Group review of the N2852 version. This paper renames [[check_names]] to [[base_check]].
We propose to introduce three new attributes to support compile-time checks that name hiding and virtual function overrides occur exactly as intended.
The [[override]]
attribute indicates that a member function
override a virtual function in at least one of its direct or indirect
base classes. If no appropriate virtual function can be found, the program
is ill-formed.
The [[hiding]]
attribute indicates that a class member hides
a name found in at least one of its direct or indirect base classes.
If no members with such a name are found, the program is ill-formed.
The [[base_check]]
attribute for a class definition
indicates that any name hiding or virtual function overriding within
that class shall be marked with the appropriate attribute. If there is
any 'implicit' overriding or hiding then the program is ill-formed.
This design meets three key compatibility requirements:
The 'simple' syntax for overriding virtual functions in C++ today is convenient, but potentially misleading. Take the following example:
struct base { virtual void some_func(); }; struct derived : base { void some_func(); };
Is the use of some_func in derived intentionally an override, or an
accidental name clash? Without clear documentation or a detailed analysis
of the use/implementation it is hard to be sure.
The common assumption is that all such use is deliberate, and to ignore
the question. Yet while it is reasonable to assume that competent developers
would not accidentally make such a mistake, it is easily introduced by
dependencies on third party libraries. Maintenance of the base classes might
introduce such a clash without clear notice in the code, and the ensuing bugs
can be very hard to track down. Worse, even a rigorous set of unit tests does
not guarantee protection, as it is the base class, not the derived, that is
changing behaviour and so the effects are not guaranteed to be covered by
existing tests. Likewise, source analysis tools can offer little help here
without an additional hint from the coder. To that end, we offer the
[[base_check]] attribute and the requirement in such a marked class that
intentional overrides use the [[override]] attribute.
struct base { virtual void some_func(); }; struct derived1 [[base_check]] : base { void some_func(); // error, accidental override with base_check attribute }; struct derived2 [[base_check]] : base { void some_func [[override]] (); // OK, override with override attribute };
A second set of problems arise when a user intends to override a function, but gets the signature wrong in some way. Common examples are mis-spelling the name (transposing two characters, bad capitalization); using the wrong data type (double instead of float); or missing the cv-qualification.
All these classes of errors can be detected in the same way. When the virtual keyword is used in a class defined with the [[base_check]] attribute, a matching declaration must be found in one of the base classes for it to override. In implementation terms it would suffice to check the inherited vtable, rather than search the full DAG of base classes looking for a match.
Note: This kind of problem should also be detected with a good set of unit tests. It is hard to solve in a lexical analysis tool without a further hint, see next point!
struct base { virtual void some_func1(); virtual void some_func2(float); virtual void some_func3() const; virtual long some_func4(int); }; struct derived : base { void sone_func1 [[override]] (); // error, mis-spelled name void some_func2 [[override]] (double); // error, bad argument type void some_func3 [[override]] (); // error, missing cv-qualification int some_func4 [[override]] (int); // ill-formed: return type does not match B::h };
In an earlier version of this paper, a [[new]] attribute was necessary to introduce a new virtual function into a name-checked class. The addition of the [[override]] attribute means that this is no longer necessary, and this paragraph is mostly to demonstrate that no features have been lost. A new virtual function is automatically introduced by using the virtual keyword and not supplying the [[override]] attribute.
struct base { virtual void some_func1(); }; struct derived [[base_check]] { virtual void some_func1(); // error, accidental override virtual void some_func2(); // OK, new virtual function introduced };
We do not propose a facility to introduce a virtual function with an identical signature to that of a virtual function in a base class, that hides rather than overrides the base member. This facility is available in other languages, but adding it to C++ at this late stage would be disruptive, and violate one of the goals of the attribute form of this proposal: Any program that compiles with the attributes will behave exactly as if they were not present. They are a pure syntax-checking device, and have no impact on semantics.
Another problem that catches people out when defining classes is that
introducing a name will hide occurrences of the same name from any base
classes. Sometimes this is intentional, but there is little chance of a
warning when it occurs accidentally. The language-supplied workaround in
those cases is a using declaration, but you only apply the workaround when
you are aware of the problem. The [[base_check]]
attribute
will require the compiler inform you of accidental hiding. The
[[hiding]]
attribute will allow you to tell the compiler that the
hiding is intentional, and the program remains well-formed.
Example:
class B { typedef B self; virtual void some_func1(); virtual void some_func2(float); virtual void some_func3() const; virtual long some_func4(int); virtual void f(); virtual void h(int); void j(int); void k(int); }; class D [[base_check]] : public B { using B::j; void sone_func1 [[override]] (); // ill-formed: mis-spelled name void some_func2 [[override]] (double); // ill-formed: bad argument type void some_func3 [[override]] (); // ill-formed: missing cv-qualification int some_func4 [[override]] (int); // ill-formed: return type does not match B::some_func4 virtual void f [[override]] (); // OK: overrides B::f virtual void g(long); // new virtual function introduced void h(int); // ill-formed: overriding without [[override]] virtual void h(double); // ill-formed: new virtual function hides void h(int) virtual void h [[hiding]] (char *); // OK, new virtual function hides base virtual int j( double ); // OK, using declaration prevents hiding int k( double ); // ill-formed: name hiding and no using declaration double k [[hiding]] ( char * ); // OK, hiding is clearly indicated double m [[hiding]] ( char * ); // ill-formed, hiding is requested, but not present typedef D self; // ill-formed, new type definition hides the definition in B };
A really determined user should be able to combine the [[hiding]] and [[override]] attributes although it seems unlikely to be used in practice beyond test suites.
struct A { virtual void fn(); }; struct B : A { void fn [[hiding]] (int); }; struct C : B { void fn [[override, hiding]](); };
7.6.5(new) Class member name checking attributesThe attribute-token
override
asserts that a virtual member function overrides a function in a base class. It shall appear at most once in each attribute-list and no attribute-argument-clause shall be present. The attribute applies to virtual member functions being declared in a class definition.If a virtual member function
f
is markedoverride
and does not override (10.3 class.virtual) a member function of a base class the program is ill-formed.The attribute-token
hiding
asserts that a class member name hides a name in a base class. It shall appear at most once in each attribute-list and no attribute-argument-clause shall be present. The attribute applies to class members being declared in a class definition.If a class member is marked
hiding
and its name does not hide (3.3.10 basic.scope.hiding, 10.2 class.member.lookup) a class member name in a base class the program is ill-formed.The attribute-token
base_check
specifies that overriding and hiding of base members is strictly checked within a class. It shall appear at most once in each attribute-list and no attribute-argument-clause shall be present. The attribute applies to a class definition.In a class definition marked
base_check
, if a virtual member function that is neither implicitly-declared nor a destructor overrides (10.3 class.virtual) a member function of a base class and it is not markedoverride
, the program is ill-formed. Similarly, in such a class definition, if a class member name other than that of an implicitly-declared special member function hides (3.3.10 basic.scope.hiding, 10.2 class.member.lookup) a class member name in a base class and it is not markedhiding
, the program is ill-formed. [ Note: A using-declaration makes the potentially hidden name visible, avoiding the need for thehiding
attribute. -- end note ][Example:
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 };end example ]