• Версия manip2(const C&) подходит потому, что у класса С есть конструктор преобразования, получающий тип int. Этот конструктор точно соответствует аргументу.

• Версия manip2(const E&) подходит потому, что у класса E есть конструктор преобразования, получающий тип double и возможность использовать стандартное преобразование для преобразования аргумента типа int, чтобы использовать этот конструктор преобразования.

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

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

Упражнения раздела 14.9.2

Упражнение 14.50. Представьте возможные последовательности преобразований типов для инициализации объектов ex1 и ex2. Объясните, допустима ли их инициализация или нет.

struct LongDouble {

 LongDouble(double = 0.0);

 operator double();

 operator float();

};

LongDouble IdObj;

int ex1 = IdObj;

float ex2 = IdObj;

Упражнение 14.51. Представьте последовательности преобразования (если они есть), необходимые для вызова каждой версии функции calc(), и объясните, как подбирается наилучшая подходящая функция.

void calc(int);

void calc(LongDouble);

double dval;

calc(dval); // которая calc()?

<p><image l:href="#books.png"/>14.9.3. Подбор функций и перегруженные операторы</p>

Перегруженные операторы — это перегруженные функции. При выявлении, который из встроенных или перегруженных операторов применяется для данного выражения, используется обычный подбор функции (см. раздел 6.4). Однако, когда в выражении используется функция оператора, набор функций-кандидатов шире, чем при вызове функций, использующих оператор вызова. Если объект а имеет тип класса, то выражение a sym b может быть следующим:

a.operator sym(b);  // класс а содержит оператор sym как функцию-член

operator sym(a, b); // оператор sym - обычная функция 

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

Когда используется перегруженный оператор с операндом типа класса, функции-кандидаты включают обычные версии, не являющиеся членами класса этого оператора, а также его встроенные версии. Кроме того, если левый операнд имеет тип класса, определенные в нем перегруженные версии оператора (если они есть) также включаются в набор кандидатов.

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

Набор функций-кандидатов для используемого в выражении оператора может содержать функции-члены и не члены класса.

Определим, например, оператор суммы для класса SmallInt:

class SmallInt {

 friend

 SmallInt operator*(const SmallInt&, const SmallInt&);

public:

 SmallInt(int = 0);                   // преобразование из int

 operator int() const { return val; } // преобразование в int

private:

 std::size_t val;

};

Этот класс можно использовать для суммирования двух объектов класса SmallInt, но при попытке выполнения смешанных арифметических операций возникнет проблема неоднозначности:

SmallInt s1, s2;

SmallInt s3 = s1 + s2; // использование перегруженного оператора +

int i = s3 + 0;        // ошибка: неоднозначность

Перейти на страницу:

Похожие книги