Неоднозначность
Наличие в одном и том же классе конвертеров, выполняющих неявные преобразования во встроенные типы, и перегруженных операторов может приводить к неоднозначности при выборе между ними. Например, есть следующее определение класса String с функцией сравнения:
class String {
// ...
public:
String( const char * = 0 );
bool operator== ( const String & ) const;
// нет оператора operator== ( const char * )
};
и такое использование оператора operator==:
String flower( "tulip" );
void foo( const char *pf ) {
// вызывается перегруженный оператор String::operator==()
if ( flower == pf )
cout << pf << " is a flower!\en";
// ...
}
Тогда при сравнении
flower == pf
вызывается оператор равенства класса String:
String::operator==( const String & ) const;
Для трансформации правого операнда pf из типа const char* в тип String параметра operator==() применяется определенное пользователем преобразование, которое вызывает конструктор:
String( const char * )
Если добавить в определение класса String конвертер в тип const char*:
class String {
// ...
public:
String( const char * = 0 );
bool operator== ( const String & ) const;
operator const char*(); // новый конвертер
};
то показанное использование operator==() становится неоднозначным:
// проверка на равенство больше не компилируется!
if (flower == pf)
Из-за добавления конвертера operator const char*() встроенный оператор сравнения
bool operator==( const char *, const char * )
тоже считается устоявшей функцией. С его помощью левый операнд flower типа String может быть преобразован в тип const char *.
Теперь для использования operator==() в foo() есть две устоявших операторных функции. Первая из них
String::operator==( const String & ) const;
требует применения определенного пользователем преобразования правого операнда pf из типа const char* в тип String. Вторая
bool operator==( const char *, const char * )
требует применения пользовательского преобразования левого операнда flower из типа String в тип const char*.
Таким образом, первая устоявшая функция лучше для левого операнда, а вторая– для правого. Поскольку наилучшей функции не существует, то вызов помечается компилятором как неоднозначный.
При проектировании интерфейса класса, включающего объявление перегруженных операторов, конструкторов и конвертеров, следует быть весьма аккуратным. Определенные пользователем преобразования применяются компилятором неявно. Это может привести к тому, что встроенные операторы окажутся устоявшими при разрешении перегрузки для операторов с операндами типа класса.
Упражнение 15.17
Назовите пять множеств функций-кандидатов, рассматриваемых при разрешении перегрузки оператора с операндами типа класса.
Упражнение 15.18
Какой из операторов operator+() будет выбран в качестве наилучшего из устоявших для оператора сложения в main()? Перечислите все функции-кандидаты, все устоявшие функции и преобразования типов, которые надо применить к аргументам для каждой устоявшей функции.
namespace NS {
class complex {
complex( double );
// ...
};
class LongDouble {
friend LongDouble operator+( LongDouble &, int ) { /* ... */ }
public:
LongDouble( int );
operator double();
LongDouble operator+( const complex & );
// ...
};
LongDouble operator+( const LongDouble &, double );
}
int main() {
NS::LongDouble ld(16.08);
double res = ld + 15.05; // какой operator+?
return 0;
}