Язык программирования C++. Вводный курс

Подробнее о стандартном преобразовании


Имеется пять видов стандартных преобразований, а именно:

1.      преобразования целых типов: приведение от целого типа или перечисления к любому другому целому типу (исключая трансформации, которые выше были отнесены к категории расширения типов);

2.      преобразования типов с плавающей точкой: приведение от любого типа с плавающей точкой к любому другому типу с плавающей точкой (исключая трансформации, которые выше были отнесены к категории расширения типов);

3.      преобразования между целым типом и типом с плавающей точкой: приведение от любого типа с плавающей точкой к любому целому типу или наоборот;

4.      преобразования указателей: приведение целого значения 0 к типу указателя или трансформация указателя любого типа в тип void*;

5.      преобразования в тип bool: приведение от любого целого типа, типа с плавающей точкой, перечислимого типа или указательного типа к типу bool.

Вот несколько примеров:

extern void print( void* );

extern void print( double );

int main() {



   int i;

   print( i );   // соответствует print( double );

                 // i подвергается стандартному преобразованию из int в double

   print( &i );  // соответствует print( void* );

                 // &i подвергается стандартному преобразованию

                 // из int* в void*

   return 0;

}

Преобразования, относящиеся к группам 1, 2 и 3, потенциально опасны, так как целевой тип может и не обеспечивать представления всех значений исходного. Например, с помощью float

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

int i;

void calc( float );

int main() {

   calc( i );   // стандартное преобразование между целым типом и типом с

                // плавающей точкой потенциально опасно в зависимости от

                // значения i

   return 0;

<
}

При вызове функции calc()

применяется стандартное преобразование из целого типа int в тип с плавающей точкой float. В зависимости от значения переменной i

может оказаться, что его нельзя сохранить в типе float без потери точности.

Предполагается, что все стандартные изменения требуют одного объема работы. Например, преобразование из char в unsigned char не более приоритетно, чем из char в double. Близость типов не принимается во внимание. Если две устоявших функции требуют для установления соответствия стандартной трансформации фактического аргумента, то вызов считается неоднозначным и помечается компилятором как ошибка. Например, если даны две перегруженные функции:



extern void manip( long );
extern void manip( float );

то следующий вызов неоднозначен:



int main() {

   manip( 3.14 );   // ошибка: неоднозначность

                    // manip( float ) не лучше, чем manip( int )

   return 0;
}

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

manip ( static_cast<long>( 3.14 ) );   // manip( long )

либо используя суффикс, обозначающий, что константа принадлежит к типу float:

manip ( 3.14F ) );   // manip( float )

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



extern void farith( unsigned int );

extern void farith( float );

int main() {

   // каждый из последующих вызовов неоднозначен

   farith( 'a' );       // аргумент имеет тип char

   farith( 0 );         // аргумент имеет тип int

   farith( 2uL );       // аргумент имеет тип unsigned long

   farith( 3.14159 );   // аргумент имеет тип double

   farith( true );      // аргумент имеет тип bool
<


}

Стандартные преобразования указателей иногда противоречат интуиции. В частности, значение 0

приводится к указателю на любой тип; полученный таким образом указатель называется нулевым. Значение 0

может быть представлено как константное выражение целого типа:



void set(int*);

int main() {

   // преобразование указателя из 0 в int* применяется к аргументам

   // в обоих вызовах

   set( 0L );

   set( 0x00 );

   return 0;
}

Константное выражение 0L

(значение 0

типа long int) и константное выражение 0x00

(шестнадцатеричное целое значение 0) имеют целый тип и потому могут быть преобразованы в нулевой указатель типа int*.

Но поскольку перечисления не относятся к целым типам, элемент, равный 0, не приводим к типу указателя:



enum EN { zr = 0 };
set( zr );   // ошибка: zr нельзя преобразовать в тип int*

Вызов функции set()

является ошибкой, так как не существует преобразования между значением zr

элемента перечисления и формальным параметром типа int*, хотя zr

равно 0.

Следует отметить, что константное выражение 0 имеет тип int. Для его приведения к типу указателя требуется стандартное преобразование. Если в множестве перегруженных функций есть функция с формальным параметром типа int, то именно в ее пользу будет разрешена перегрузка в случае, когда фактический аргумент равен 0:



void print( int );

void print( void * );

void set( const char * );

void set( char * );

int main () {

   print( 0 );   // вызывается print( int );

   set( 0 );     // неоднозначность

   return 0;
}

При вызове print(int)

имеет место точное соответствие, тогда как для вызова print(void*)

необходимо приведение значения 0 к типу указателя. Поскольку соответствие лучше преобразования, для разрешения этого вызова выбирается функция print(int). Обращение к set()

неоднозначно, так как 0

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



Последнее из возможных преобразований указателя позволяет привести указатель любого типа к типу void*, поскольку void* – это родовой указатель на любой тип данных. Вот несколько примеров:



#include <string>

extern void reset( void * );

void func( int *pi, string *ps ) {

   // ...

   reset( pi );   // преобразование указателя: int* в void*

   /// ...

   reset( ps );   // преобразование указателя: string* в void*
}

Только указатели на типы данных могут быть приведены к типу void* с помощью стандартного преобразования, с указателями на функции так поступать нельзя:



typedef int (*PFV)();

extern PFV testCases[10];   // массив указателей на функции

extern void reset( void * );

int main() {

   // ...

   reset( textCases[0] );  // ошибка: нет стандартного преобразования

                           // между int(*)() и void*

   return 0;
}


Содержание раздела