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

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

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

class A;

class B { 
      public: 
         B(){} 

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

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

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

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

Мои вопросы:

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

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

4b9b3361

Ответ 1

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

B(const A&)
operator B() 

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

B(const A&)
B(A&)

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

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

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


Стандарт, 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", возвращают lvalues ​​типа X и поэтому считаются выходными X для этого процесса выбора функций-кандидатов.

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

И 13.3.3.2/3

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

Ответ 2

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

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

A a;

B b = a;