extern void func( Number );
SmallInt si(87);
int main()
{ // ошибка: не существует неявного преобразования из SmallInt в Number
func( si );
// ...
}
Однако такой конструктор все же можно использовать для преобразования типов, если оно запрошено явно в форме оператора приведения типа:
SmallInt si(87);
int main()
{ // ошибка: не существует неявного преобразования из SmallInt в Number
func( si );
func( Number( si ) ); // правильно: приведение типа
func( static_cast Number ( si ) ); // правильно: приведение типа
}
15.10. Выбор преобразования A
Определенное пользователем преобразование реализуется в виде конвертера или конструктора. Как уже было сказано, после преобразования, выполненного конвертером, разрешается использовать стандартное преобразование для приведения возвращенного значения к целевому типу. Трансформации, выполненной конструктором, также может предшествовать стандартное преобразование для приведения типа аргумента к типу формального параметра конструктора.
Последовательность определенных пользователем преобразований – это комбинация определенного пользователем и стандартного преобразования, которая необходима для приведения значения к целевому типу. Такая последовательность имеет вид:
Последовательность стандартных преобразований -
Определенное пользователем преобразование -
Последовательность стандартных преобразований
где определенное пользователем преобразование реализуется конвертером либо конструктором.
Не исключено, что для трансформации исходного значения в целевой тип существует две разных последовательности пользовательских преобразований, и тогда компилятор должен выбрать из них лучшую. Рассмотрим, как это делается.
В классе разрешается определять много конвертеров. Например, в нашем классе 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).