Ссылки
Фактический аргумент или формальный параметр функции могут быть ссылками. Как это влияет на правила преобразования типов?
Рассмотрим, что происходит, когда ссылкой является фактический аргумент. Его тип никогда не бывает ссылочным. Аргумент-ссылка трактуется как 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, то для ее инициализации такие переменные использовать нельзя.
Вот еще один пример, в котором между формальным параметром-ссылкой и фактическим аргументом нет соответствия:
class B;
void takeB( B& );
B giveB();
int main() {
takeB( giveB() ); // ошибка: параметр должен быть типа const B &
return 0;
}
Вызов функции takeB() – ошибка. Фактический аргумент – это возвращаемое значение, т.е. временная переменная, которая не может быть использована для инициализации ссылки без спецификатора const.
В обоих случаях мы видим, что если формальный параметр-ссылка имеет спецификатор const, то между ним и фактическим аргументом может быть установлено точное соответствие.
Следует отметить, что и преобразование l-значения в r-значение, и инициализация ссылки считаются точными соответствиями. В данном примере первый вызов функции приводит к ошибке:
void print( int );
void print( int& );
int iobj;
int &ri = iobj;
int main() {
print( iobj ); // ошибка: неоднозначность
print( ri ); // ошибка: неоднозначность
print( 86 ); // правильно: вызывается print( int )
return 0;
}
Объект iobj – это аргумент, для которого может быть установлено соответствие с обеими функциями print(), то есть вызов неоднозначен. То же относится и к следующей строке, где ссылка ri обозначает объект, соответствующий обеим функциям print(). С третьим вызовом, однако, все в порядке. Для него print(int&) не является устоявшей. Целая константа – это r-значение, так что она не может инициализировать параметр-ссылку. Единственной устоявшей функцией для вызова print(86) является print(int), поэтому она и выбирается при разрешении перегрузки.
Короче говоря, если формальный параметр представляет собой ссылку, то для фактического аргумента точное соответствие устанавливается, если он может инициализировать ссылку, и не устанавливается в противном случае.
Упражнение 9.6
Назовите два тривиальных преобразования, допустимых при установлении точного соответствия.
Упражнение 9.7
Каков ранг каждого из преобразований аргументов в следующих вызовах функций:
(a) void print( int *, int );
int arr[6];
print( arr, 6 ); // вызов функции
(b) void manip( int, int );
manip( 'a', 'z' ); // вызов функции
(c) int calc( int, int );
double dobj;
double = calc( 55.4, dobj ) // вызов функции
(d) void set( const int * );
int *pi;
set( pi ); // вызов функции
Упражнение 9.8
Какие из данных вызовов ошибочны из-за того, что не существует преобразования между типом фактического аргумента и формального параметра:
(a) enum Stat { Fail, Pass };
void test( Stat );
text( 0 ); // вызов функции
(b) void reset( void *);
reset( 0 ); // вызов функции
(c) void set( void * );
int *pi;
set( pi ); // вызов функции
(d) #include <list>
list<int> oper();
void print( oper() ); // вызов функции
(e) void print( const int );
int iobj;
print( iobj ); // вызов функции