6

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?

curiousguy
  • 8,038
  • 2
  • 40
  • 58
ixSci
  • 13,100
  • 5
  • 45
  • 79

1 Answers1

6

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) as Base​::​vf is declared, then Derived​::​vf is also virtual (whether or not it is so declared) and it overrides Base​::​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.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
  • In other words, the using-statement declares a name but not a distinct function, so although we can access the function via its name in `C`, there is nothing to become an overrider because no distinct function in `C` exists. – Lightness Races in Orbit Oct 29 '18 at 10:54
  • Hm, ok I see your logic but if it is not UB then what it should call and why? I mean `d.print()` is a virtual call and it should decide what function to call and we have 2. What to do? – ixSci Oct 29 '18 at 11:43
  • @ixSci - `d.print()` must first do lookup to determine which function to call. Since your code has a `using C::print;` declaration, that's the `print` that will be found unambiguously. Now, since `d` is not a pointer or reference, it will be dispatched normally. The using declaration allows you to determine how name lookup will work, but it does not introduce function declarations. Does that make sense? – StoryTeller - Unslander Monica Oct 29 '18 at 11:45
  • @StoryTeller, it somewhat does. But you can change the type to be a reference or a pointer and the same question still applies. Moreover, if you look at the first example in my question (excerpt from the Standard) you can see the same pattern but it calls `B::f` not `A::f` doing dynamic dispatch not using the info it got from using declaration. – ixSci Oct 29 '18 at 11:52
  • @ixSci - If `d` was a `D&` than a virtual function call will need to happen in order to call an override of `C::f`. The type of `d` will affect the call, but the identity of the function to call (possibly virtually) is determined in part by the using declaration. Every virtual function call is done in two parts, kinda. Name lookup + overload resolution to find the function name to call (that is affected by using declarations), followed by a virtual function call (that is affected by overriding, but not using declarations). The example in your question is inline with that, I'd say. – StoryTeller - Unslander Monica Oct 29 '18 at 11:56
  • Well, to my understanding it contradicts the fact that using declaration doesn't provide final overriders. Because if it doesn't it can't participate in virtual dispatch resolution. There is also a quote in older Standards (at least in 03) which says the following: _"The rules for member lookup (10.2) are used to determine the final overrider for a virtual function in the scope of a derived class but ignoring names introduced by using-declarations."_ I do not understand why they removed that but it still was there. – ixSci Oct 29 '18 at 12:00
  • 1
    @ixSci - The wording isn't there because it was the subject of a defect ([608](http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#608)). You shouldn't hang on to those words. The new wording is introduced precisely with the intent of differentiating overriders from the identity of what they override (and identity that can be determined by a using declaration). – StoryTeller - Unslander Monica Oct 29 '18 at 12:07
  • Thanks for the explanation and this defect report! Please add it (I mean the ref to DR) to the answer as it helps to complete the picture. I will ponder about it for some time and accept your answer. – ixSci Oct 29 '18 at 12:13
  • @ixSci - Sure. Added a word about the report. – StoryTeller - Unslander Monica Oct 29 '18 at 12:18
  • As per this, isn't there a less verbose way to say that a virtual function should use a specific base class implementation? – Treviño Oct 16 '20 at 12:21
  • @Treviño - Not that I'm aware of – StoryTeller - Unslander Monica Oct 16 '20 at 14:15