Выбрать главу

Последовательность определенных пользователем преобразований – это комбинация определенного пользователем и стандартного преобразования, которая необходима для приведения значения к целевому типу. Такая последовательность имеет вид:

Последовательность стандартных преобразований -

Определенное пользователем преобразование -

Последовательность стандартных преобразований

где определенное пользователем преобразование реализуется конвертером либо конструктором.

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

В классе разрешается определять много конвертеров. Например, в нашем классе Number их два: operator int() и operator float(), причем оба способны преобразовать объект типа Number в значение типа float. Естественно, можно воспользоваться конвертером Token::operator float() для прямой трансформации. Но и Token::operator int() тоже подходит, так как результат его применения имеет тип int и, следовательно, может быть преобразован в тип float с помощью стандартного преобразования. Является ли трансформация неоднозначной, если имеется несколько таких последовательностей? Или какую-то из них можно предпочесть остальным?

class Number {

public:

operator float();

operator int();

// ...

};

Number num;

float ff = num; // какой конвертер? operator float()

* В таких случаях выбор наилучшей последовательности определенных пользователем преобразований основан на анализе последовательности преобразований, которая применяется после конвертера. В предыдущем примере можно применить такие две последовательности: operator float() - точное соответствие

* operator int() - стандартное преобразование

Как было сказано в разделе 9.3, точное соответствие лучше стандартного преобразования. Поэтому первая последовательность лучше второй, а значит, выбирается конвертер Token::operator float().

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

class SmallInt {

public:

SmallInt( int ival ) : value( ival ) { }

SmallInt( double dval )

: value( static_cast int ( dval ) );

{ }

};

extern void manip( const SmallInt & );

int main() {

double dobj;

manip( dobj ); // правильно: SmallInt( double )

}

* Здесь в классе SmallInt определено два конструктора – SmallInt(int) и SmallInt(double), которые можно использовать для изменения значения типа double в объект типа SmallInt: SmallInt(double) трансформирует double в SmallInt напрямую, а SmallInt(int) работает с результатом стандартного преобразования double в int. Таким образом, имеются две последовательности определенных пользователем преобразований: точное соответствие - SmallInt( double )

* стандартное преобразование - SmallInt( int )

Поскольку точное соответствие лучше стандартного преобразования, то выбирается конструктор SmallInt(double).

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

class Number {

public:

operator float();

operator int();

// ...

};

то невозможно неявно преобразовать объект типа Number в тип long. Следующая инструкция вызывает ошибку компиляции, так как выбор последовательности определенных пользователем преобразований неоднозначен:

// ошибка: можно применить как float(), так и int()

long lval = num;

* Для трансформации num в значение типа long применимы две такие последовательности: operator float() - стандартное преобразование operator int() - стандартное преобразование

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

С помощью явного приведения типов программист способен задать нужное изменение:

// правильно: явное приведение типа