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

Следует отметить, что константное выражение 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;

}

9.3.4. Ссылки

Фактический аргумент или формальный параметр функции могут быть ссылками. Как это влияет на правила преобразования типов?

Рассмотрим, что происходит, когда ссылкой является фактический аргумент. Его тип никогда не бывает ссылочным. Аргумент-ссылка трактуется как l-значение, тип которого совпадает с типом соответствующего объекта:

int i;

int ri = i;

void print( int );

int main() {

print( i ); // аргумент - это lvalue типа int

print( ri ); // то же самое

return 0;

}

Фактический аргумент в обоих вызовах имеет тип int. Использование ссылки для его передачи во втором вызове не влияет на сам тип аргумента.

Стандартные преобразования и расширения типов, рассматриваемые компилятором, одинаковы для случаев, когда фактический аргумент является ссылкой на тип T и когда он сам имеет такой тип. Например:

int i;

int ri = i;

void calc( double );

int main() {

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

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

calc( ri ); // то же самое

return 0;

}

А как влияет на преобразования, применяемые к фактическому аргументу, формальный параметр-ссылка? Сопоставление дает следующие результаты:

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

void swap( int , int );

void manip( int i1, int i2 ) {

// ...

swap( i1, i2 ); // правильно: вызывается swap( int , int )

// ...

return 0;

}

*

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

int obj;

void frd( double );

int main() {

frd( obj ); // ошибка: параметр должен иметь иметь тип const double

return 0;

}

*

Вызов функции frd() является ошибкой. Фактический аргумент имеет тип int и должен быть преобразован в тип double, чтобы соответствовать формальному параметру-ссылке. Результатом такой трансформации является временная переменная. Поскольку ссылка не имеет спецификатора const, то для ее инициализации такие переменные использовать нельзя.

* Вот еще один пример, в котором между формальным параметром-ссылкой и фактическим аргументом нет соответствия: