Странный синтаксис при переопределении виртуальных функций

Чтение этого ответа, я был достаточно удивлен, чтобы попробовать себя, если он действительно работает, как описано:

#include <iostream>

class A {
public:
    virtual void foo() = 0;
};

class B {
public:
    virtual void foo() = 0;
};

class C : public A, public B {
public:
    virtual void foo();
};

void C::foo(){
  std::cout << "C" << std::endl;
}
void C::A::foo(){
  std::cout << "A" << std::endl;
}
void C::B::foo(){
  std::cout << "B" << std::endl;
}

int main() {
    C c;
    static_cast<A*>(&c)->foo();
    static_cast<B*>(&c)->foo();
    c.foo();
    return 0;
}

Я действительно не думал, что можно переопределить виртуальный метод из двух разных базовых классов, имеющих одно и то же имя и подпись. И, как я ожидал, над программами печатает:

C
C
C

Таким образом, ответ неправильный - как я чувствовал с самого начала. Удивительная часть: почему мой gcc принимает этот синтаксис: void C::A::foo(){? Я не мог найти ничего, что это могло означать. Это ошибка/особенность gcc? Это какой-то неясный стандартный синтаксис С++? Или я полностью неверно истолковал ситуацию?

EDIT:

Кажется, что void C::A::foo(){} - это просто определение для A::foo в этом контексте. Но почему? Это ошибка GCC? Или это как-то разрешено стандартом? Если да, то это конкретное правило для такого рода вещей или какое-то общее предложение (скажем: если идентификатор не имеет смысла - например, "C:: A:: foo", тогда компилятор может делать то, что он хочет).

Ответы

Ответ 1

Я думаю, что факт, что C::A::foo определяет A::foo, вызван именем injected-class.

N3337 [class]/2: Имя класса вставляется в область, в которой она объявляется сразу после просмотра имени класса. Имя класса также вставляется в область самого класса; это известно как имя введенного класса. Для проверки доступа имя введенного класса рассматривается как имя публичного участника. [...]

Так как A является базовым классом C и имя A вводится в область A, A также видно из C.

Учитывая вышеизложенное, мы можем делать ужасно извращенные вещи, такие как:

void C::C::C::A::A::A::foo(){
    std::cout << "A" << std::endl;
}

Live Demo

Ответ 2

Интересно, что

void C::A::foo(){
  std::cout << "A" << std::endl;
}

определяет A::foo() (вы можете предоставить реализацию для чистой виртуальной функции, если хотите). Вы можете проверить, что это действительно так, добавив дополнительное определение:

void A::foo(){
  std::cout << "Base A" << std::endl;
}
void C::A::foo(){
  std::cout << "A" << std::endl;
}

GCC сообщит об ошибке множественного определения. Это, вероятно, имеет смысл, потому что вы можете посмотреть C::A как псевдоним имени для A.

В терминах, о которых говорится в стандарте, я в настоящее время изо всех сил пытаюсь найти точное определение того, когда это уместно в море стандартизированных, но я вижу связанный пример в Пункт 3.4.3.1.2:

struct A { A(); };
struct B: public A { B(); };
A::A() { }
B::B() { }
B::A ba; // object of type A // <-- here
A::A a; // error, A::A is not a type name
struct A::A a2; // object of type A

Пример заключается в разрешении конструктора, но конструктор - это в конечном счете метод, поэтому я предполагаю, что GCC использовал один и тот же механизм для каждого разрешения области.

Ответ 3

Компилятор Visual Studio (2008) не разрешает

void C::A::foo(){
  std::cout << "A" << std::endl;
}
void C::B::foo(){
  std::cout << "B" << std::endl;

но только

void A::foo(){
  std::cout << "A" << std::endl;
}
void B::foo(){
  std::cout << "B" << std::endl;

поскольку он полностью действителен, чтобы дать определение для чистых виртуальных методов.

"Эффективный С++" Майерс упоминает причину чистой виртуальной функции иметь тело: производные классы, реализующие этот чистый виртуальный функция может вызывать эту реализацию smwhere в своем коде. Если часть кода двух разных производных классов аналогичны, то это делает смысл перемещать его в иерархии, даже если функция должна быть чистый виртуальный.

Смотрите здесь (forum.codeguru.com).

Чистую виртуальную функцию нельзя назвать динамически, но статически:

C c;
static_cast<A*>(&c)->foo(); // prints C
static_cast<B*>(&c)->foo(); // prints C, dynamic dispatch
c.foo();                    // prints C, dynamic dispatch
c.A::foo();                 // prints A, static dispatch
c.B::foo();                 // prints B, static dispatch

Пояснение:

Когда вы вызываете виртуальную функцию, используя ее полное имя ( class-name, за которым следует "::" ), компилятор не использует виртуальную но вместо этого использует тот же механизм, как если бы вы вызывали не виртуальная функция. С другой стороны, он вызывает функцию по имени а не слотом. Поэтому, если вы хотите, чтобы код в производном классе Der, чтобы вызвать Base:: f(), то есть версию f(), определенную в ее базе класс Base, вы должны написать:

void Derived::f()
{ Base::f(); }

См. здесь (isocpp.org).