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


чтение некоторых вопросов здесь о 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;
}

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

мои вопросы:

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

2 ответа:

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

B(const A&)
operator B() 

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

B(const A&)
B(A&)

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

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

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

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

и 13.3.3.2/3

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

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

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

A a;

B b = a;