We have the following simple (and slightly modified to add main
and output) example in the Standard:
struct A {
virtual void f()
{
cout << "A\n";
}
};
struct B : virtual A {
virtual void f()
{
cout << "B\n";
}
};
struct C : B, virtual A {
using A::f;
};
int main()
{
C c;
c.f(); // calls B::f, the final overrider
c.C::f();
return 0;
}
From which we can make a conclusion that using A::f
does not present an overrider. But what wording in the Standard dictates it? Here is the wording for the final overrider from the C++17 draft ([class.virtual]p2):
<...> A virtual member function C::vf of a class object S is a final overrider unless the most derived class (4.5) of which S is a base class subobject (if any) declares or inherits another member function that overrides vf. In a derived class, if a virtual member function of a base class subobject has more than one final overrider the program is ill-formed.
And I wasn't able to find what "overrides" actually mean. If it is not defined and we consider any declaration as an overrider then we should consider the using declaration to be an overrider since [namespace.udecl]p2 says:
Every using-declaration is a declaration and a member-declaration and can therefore be used in a class definition.
I understand the intent of the Standard for using declaration to not introduce an overrider, but can someone point me to the actual quotes which say that in Standardese? That is the first part, now to the second one
Consider the following code:
#include <iostream>
#include <string>
using std::cout;
class A {
public:
virtual void print() const {
cout << "from A" << std::endl;
}
};
class B: public A {
public:
void print() const override {
cout << "from B" << std::endl;
}
};
class C: public A {
public:
void print() const override {
cout << "from C" << std::endl;
}
};
class D: public B, public C {
public:
using C::print;
};
int main()
{
D d{};
d.print();
return 0;
}
If the using declaration doesn't introduce an overrider then we have 2 final overriders in D
, hence—an undefined behavior because of
In a derived class, if a virtual member function of a base class subobject has more than one final overrider the program is ill-formed.
Right?
A using declaration, while indeed a declaration as far as declarative regions are concerned, is not a function declaration. We can see it specified grammatically:
[dcl.dcl]
1 Declarations generally specify how names are to be interpreted. Declarations have the form
declaration: block-declaration nodeclspec-function-declaration function-definition template-declaration deduction-guide explicit-instantiation explicit-specialization linkage-specification namespace-definition empty-declaration attribute-declaration block-declaration: simple-declaration asm-definition namespace-alias-definition using-declaration using-directive static_assert-declaration alias-declaration opaque-enum-declaration nodeclspec-function-declaration: attribute-specifier-seq declarator ;
And to some extent semantically. Since the following paragraphs detail how a using declaration that introduces member functions from a base class is different to member function declarations in the derived class.
[namespace.udecl]
15 When a using-declarator brings declarations from a base class into a derived class, member functions and member function templates in the derived class override and/or hide member functions and member function templates with the same name, parameter-type-list, cv-qualification, and ref-qualifier (if any) in a base class (rather than conflicting). Such hidden or overridden declarations are excluded from the set of declarations introduced by the using-declarator.
16 For the purpose of overload resolution, the functions that are introduced by a using-declaration into a derived class are treated as though they were members of the derived class. In particular, the implicit this parameter shall be treated as if it were a pointer to the derived class rather than to the base class. This has no effect on the type of the function, and in all other respects the function remains a member of the base class.
With that in mind, if one takes into account the beginning of the first paragraph you quote:
[class.virtual]
2 If a virtual member function
vf
is declared in a class Base and in a class Derived, derived directly or indirectly from Base, a member function vf with the same name, parameter-type-list, cv-qualification, and ref-qualifier (or absence of same) asBase::vf
is declared, thenDerived::vf
is also virtual (whether or not it is so declared) and it overridesBase::vf
. For convenience we say that any virtual function overrides itself.
We can see that it is a virtual function declaration that can introduce an overrider to a virtual function in a base class. And since a using declaration is not a function declaration, it does not qualify.
The current wording is in part from CWG Defect 608. It is aimed to clarify the problematic interpretation in that report, and to decouple using declarations from the notion of virtual function overriders.
As for your second question, what's important to note in that quote is "of a base class subobject". Your code sample has two A
sub-objects in D
(the inheritance in that example isn't virtual). And each one has its own final overrider in B
and C
respectively. So the program is not ill-formed, with or without another overrider being declared in D
.
The paragraph you are concerned with applies to the case of virtual inheritance. If B
and C
had a virtual A
base, and D
inherited from both without overriding print
, the program would be ill-formed. And a using declaration like using C::print
will not make it well-formed, again due to the reasons stated above.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With