Подтвердить что ты не робот

C + + перегрузка и полиморфизм оператора

Я озадачен этим поведением С++:

struct A {
   virtual void print() const { printf("a\n"); }
};

struct B : public A {
   virtual void print() const { printf("b\n"); }
};

struct C {
   operator B() { return B(); }
};

void print(const A& a) {
   a.print();
}

int main() {
   C c;
   print(c);
}

Итак, опрос - это то, что является выходом программы - a или b? Ну, ответ: a. Но почему?

4b9b3361

Ответ 1

Проблема заключается в ошибке/ошибке или ошибке в стандарте С++ 03, причем разные компиляторы пытаются решить проблему по-разному. (Эта проблема больше не существует в стандарте С++ 11.)

В разделах 8.5.3/5 обоих стандартов указано, как инициализируется эта ссылка. Здесь версия С++ 03 (нумерация списка - моя):

Ссылка на тип cv1 T1 инициализируется выражением типа cv2 T2 следующим образом:

  • Если выражение инициализатора

    • является lvalue (но не является битовым полем), а "cv1 T1" ссылается на "cv2 T2" или
    • имеет тип класса (т.е. T2 - тип класса) и может быть неявно преобразован в lvalue типа cv3 T3, где cv1 T1 является ссылочным-совместимым с cv3 T3

    тогда ссылка привязана непосредственно к значению инициализатора lvalue в первом случае, а ссылка привязана к lvalue-результату преобразования во втором случае.

  • В противном случае эта ссылка должна быть указана в виде энергонезависимого типа (т.е. cv1 должна быть const).

  • Если выражение инициализатора является rvalue, T2 тип класса, а cv1 T1 является ссылочным-совместимым с cv2 T2, ссылка привязана одним из следующих способов (выбор - реализация -определенный):

    • Ссылка привязана к объекту, представленному rvalue (см. 3.10), или к под-объекту внутри этого объекта.
    • Создается временный тип cv1 T2 [sic], и вызывается конструктор для копирования всего объекта rvalue во временный. Ссылка привязана к временному или к под-объекту во временном.

    Конструктор, который будет использоваться для создания копии, должен быть вызван независимо от того, действительно ли выполнена копия.

  • В противном случае временный тип cv1 T1 создается и инициализируется из выражения инициализатора, используя правила для инициализации без ссылки (8.5). Ссылка затем привязана к временному.

В рассматриваемом вопросе участвуют три типа:

  • Тип создаваемой ссылки. Стандарты (обе версии) обозначают этот тип как T1. В этом случае это struct A.
  • Тип выражения инициализатора. Стандарты обозначают этот тип как T2. В этом случае выражение инициализатора представляет собой переменную c, поэтому T2 - struct C. Обратите внимание, что поскольку struct A не является ссылочным-совместимым с struct C, невозможно напрямую привязать ссылку к c. Требуется промежуточное звено.
  • Тип промежуточного. Стандарты обозначают этот тип как T3. В этом случае это struct B. Обратите внимание, что применение оператора преобразования C::operator B() to c преобразует значение lvalue c в значение r.

Инициализации на то, что я обозначил как 1.1 и 3, отсутствуют, потому что struct A не является ссылочным-совместимым с struct C. Необходимо использовать оператор преобразования C::operator B(). 1.2 не работает. Поскольку этот оператор преобразования возвращает rvalue, это правило 1.2. Все, что осталось, это вариант 4, создать временный тип cv1 T1. Строгое соблюдение версии стандарта 2003 года заставляет создавать две временные проблемы для этой проблемы, хотя достаточно только одного.

Версия стандарта 2011 года устраняет проблему путем замены опции 3 на

  • Если выражение инициализатора

    • - это значение xvalue, класс prvalue, значение prvalue массива или функция lvalue и cv1 T1совместим с cv2 T2 или
    • имеет тип класса (т.е. T2 - тип класса), где T1 не относится к T2, а может быть неявно преобразован в значение xvalue, класс prvalue или функцию lvalue типа cv3 T3, где cv1 T1 является ссылочным-совместимым с cv3 T3,

    тогда ссылка привязана к значению выражения инициализатора в первом случае и к результату преобразования во втором случае (или, в любом случае, к соответствующему подобъекту базового класса). Во втором случае, если ссылка является ссылкой на rvalue, а вторая стандартная последовательность преобразований определяемой пользователем последовательности преобразования включает в себя преобразование lvalue-to-rval, программа плохо сформирована.

Похоже, что компиляторы семейства gcc выбрали строгое соответствие требованиям (избегайте создания ненужных временных рядов), в то время как другие компиляторы, которые печатают "b", выбрали намерение/исправление стандарта. Выбор строгого соблюдения не обязательно заслуживает похвалы; есть другие ошибки/ошибки в версии стандарта 2003 года (например, std::set), где семья gcc выбрала здравый смысл в отношении строгого соответствия.