Конструктор преобразования против оператора преобразования: приоритет

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

class A;

class B { 
      public: 
         B(){} 

         B(const A&) //conversion constructor
         { 
              cout << "called B's conversion constructor" << endl; 
         } 
};

class A { 
      public: 
         operator B() //conversion operator
         { 
              cout << "called A's conversion operator" << endl; 
              return B(); 
         } 
};

int main()
{
    B b = A(); //what should be called here? apparently, A::operator B()
    return 0;
}

Приведенный выше код отображает "оператор преобразования вызываемого А", что означает, что оператор преобразования вызывается в отличие от конструктора. Если вы удалите / закомментируете operator B() код из Aкомпилятор с радостью переключится на использование конструктора (без других изменений в коде).

Мои вопросы:

  1. Поскольку компилятор не учитывает B b = A(); чтобы быть неоднозначным вызовом, здесь должен быть какой-то приоритет. Где именно этот приоритет установлен? (ссылка / цитата из стандарта C++ приветствуется)
  2. С объектно-ориентированной философской точки зрения должен ли код вести себя? Кто знает больше о том, как A объект должен стать B объект, A или же B? Согласно C++, ответ A - есть ли что-то в объектно-ориентированной практике, которая предполагает, что это должно быть так? Лично для меня это будет иметь смысл в любом случае, поэтому мне интересно узнать, как был сделан выбор.

заранее спасибо

2 ответа

Решение

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

B(const A&)
operator B() 

Вот так вы их объявляете. Разрешение перегрузки абстрагируется от этого и преобразует каждого кандидата в список параметров, соответствующих аргументам вызова. Параметры

B(const A&)
B(A&)

Второй - потому что функция преобразования является функцией-членом. A& это так называемый неявный объектный параметр, который генерируется, когда кандидат является функцией-членом. Теперь аргумент имеет тип A, При связывании неявного параметра объекта неконстантная ссылка может привязываться к значению r. Итак, другое правило гласит, что если у вас есть две жизнеспособные функции, чьи параметры являются ссылками, тогда победит кандидат с наименьшей квалификацией const. Вот почему ваша функция преобразования выигрывает. Попробуйте сделать operator B постоянная функция-член. Вы заметите двусмысленность.

С объектно-ориентированной философской точки зрения должен ли код вести себя? Кто знает больше о том, как объект A должен стать объектом B, A или B? Согласно C++ ответ A - есть ли что-то в объектно-ориентированной практике, которая предполагает, что это должно быть так? Лично для меня это будет иметь смысл в любом случае, поэтому мне интересно узнать, как был сделан выбор.

Для записи, если вы сделаете функцию преобразования константной функцией-членом, то GCC выберет конструктор (поэтому GCC, кажется, считает, что B имеет больше дела с этим?). Переключиться в педантичный режим (-pedantic) чтобы это стало причиной диагностики.


Standardese, 8.5/14

В противном случае (т. Е. Для остальных случаев инициализации копирования) определяемые пользователем последовательности преобразования, которые могут преобразовывать из типа источника в тип назначения или (когда используется функция преобразования) в его производный класс, перечисляются, как описано в 13.3.1.4, а лучший выбирается с помощью разрешения перегрузки (13.3).

А также 13.3.1.4

Разрешение перегрузки используется для выбора определяемого пользователем преобразования, которое будет вызвано. Предполагая, что "cv1 T" является типом инициализируемого объекта, а T - тип класса, функции-кандидаты выбираются следующим образом:

  • Конвертирующие конструкторы (12.3.1) из T являются функциями-кандидатами.
  • Когда типом выражения инициализатора является тип класса "cv S", рассматриваются функции преобразования S и его базовых классов. Те, которые не скрыты в S и дают тип, чья cv-неквалифицированная версия имеет тот же тип, что и T, или является ее производным классом, являются функциями-кандидатами. Функции преобразования, которые возвращают "ссылку на X", возвращают l-значения типа X, и поэтому считается, что они дают X для этого процесса выбора функций-кандидатов.

В обоих случаях список аргументов имеет один аргумент, который является выражением инициализатора. [Примечание: этот аргумент будет сравниваться с первым параметром конструкторов и с неявным параметром объекта функций преобразования. ]

А также 13.3.3.2/3

  • Стандартная последовательность преобразования S1 является лучшей последовательностью преобразования, чем стандартная последовательность преобразования S2, если [...] S1 и S2 являются ссылочными привязками (8.5.3), и типы, на которые ссылаются ссылки, являются одним и тем же типом, за исключением высшего уровня cv-qualifiers, и тип, к которому относится ссылка, инициализированная с помощью S2, является более квалифицированным по cv, чем тип, к которому относится ссылка, инициализированная с помощью S1.

Кажется, у MSVS2008 есть свое мнение о выборе конструктора: он вызывает конструктор копирования в B независимо от константности оператора A. Так что будьте осторожны, даже если стандарт определяет правильное поведение.

Я думал, что MSVS просто ищет подходящий конструктор перед оператором преобразования, но потом обнаружил, что он начинает вызывать оператор A B(), если вы удалите слово const из конструктора B. Вероятно, это имеет какое-то особое поведение для временных, потому что следующий код все еще вызывает конструктор B:

A a;

B b = a;
Другие вопросы по тегам