Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
C++ для начинающих.pdf
Скачиваний:
183
Добавлен:
01.05.2014
Размер:
3.97 Mб
Скачать

9.3. Преобразования типов аргументов A

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

точное соответствие. Тип фактического аргумента точно соответствует типу формального параметра. Например, если в множестве перегруженных функций

void print( unsigned int );

void print( const char* );

print() есть такие: void print( char );

unsigned int a;

 

print( 'a' );

// соответствует print( char );

print( "a" );

// соответствует print( const

char* );

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

print( a );

// соответствует print( unsigned int );

соответствие с преобразованием типа. Тип фактического аргумента не соответствует типу формального параметра, но может быть преобразован в

void

 

ff(

 

char

 

);

него:

// аргумент типа int приводится к типу char

ff( 0 );

отсутствие соответствия. Тип фактического аргумента не может быть приведен к типу формального параметра в объявлении функции, поскольку необходимого преобразования не существует. Для каждого из следующих двух вызовов функции print() соответствия нет:

// функции print() объявлены так же, как и выше

int *ip;

class SmallInt { /* ... */ }; SmallInt si;

print( ip );

// ошибка: нет соответствия

print( si );

// ошибка: нет соответствия

Для установления точного соответствия тип фактического аргумента необязательно должен совпадать с типом формального параметра. К аргументу могут быть применены некоторые тривиальные преобразования, а именно:

преобразование l-значения в r-значение;

преобразование массива в указатель;

преобразование функции в указатель;

преобразования спецификаторов. (Подробнее они рассмотрены ниже.)

Категория соответствия с преобразованием типа является наиболее сложной. Необходимо рассмотреть несколько видов такого приведения: расширение типов

(promotions), стандартные преобразования и определенные пользователем преобразования. (Расширения типов и стандартные преобразования изучаются в этой главе. Определенные пользователем преобразования будут представлены позднее, после детального рассмотрения классов; они выполняются конвертером, функцией-членом, которая позволяет определить в классе собственный набор “стандартных” трансформаций. В главе 15 мы познакомимся с такими конвертерами и с тем, как они влияют на разрешение перегрузки функций.)

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

9.3.1. Подробнее о точном соответствии

Самый простой случай возникает тогда, когда типы фактических аргументов совпадают с типами формальных параметров. Например, есть две показанные ниже перегруженные функции max(). Тогда каждый из вызовов max() точно соответствует одному из объявлений:

 

int max( int, int );

 

 

 

 

double max( double, double );

 

int i1;

 

 

void calc( double d1 ) {

 

max( 56, i1 );

// точно соответствует max( int, int );

 

max( d1, 66.9 );

// точно соответствует max( double,

 

double );

 

 

}

 

Перечислимый тип точно

соответствует только определенным в нем элементам

enum Tokens { INLINE = 128; VIRTUAL = 129; }; Tokens curTok = INLINE;

enum Stat { Fail, Pass };

extern void ff( Tokens ); extern void ff( Stat ); extern void ff( int );

int main() {

// точно соответствует ff( Stat )

ff( Pass );

ff(

0 );

// точно соответствует ff( int )

ff(

curTok );

// точно соответствует

ff( Tokens )

// ...

перечисления, а также объектам, которые объявлены как принадлежащие к этому типу:

}

Выше уже упоминалось, что фактический аргумент может точно соответствовать формальному параметру, даже если для приведения их типов необходимо некоторое тривиальное преобразование, первое из которых – преобразование l-значения в r- значение. Под l-значением понимается объект, удовлетворяющий следующим условиям:

можно получить адрес объекта;

можно получить значение объекта;

это значение легко модифицировать (если только в объявлении объекта нет спецификатора const).

Напротив, r-значение – это выражение, значение которого вычисляется, или выражение, обозначающее временный объект, для которого нельзя получить адрес и значение которого нельзя модифицировать. Вот простой пример:

int calc( int );

int main() {

int lval, res;

lval = 5; // lvalue: lval; rvalue: 5 res = calc( lval );

//lvalue: res

//rvalue: временный объект для хранения

значения,

//возвращаемого функцией calc()

return 0;

}

Впервом операторе присваивания переменная lval – это l-значение, а литерал 5 – r- значение. Во втором операторе присваивания res – это l-значение, а временный объект, в котором хранится результат, возвращаемый функцией calc(), – это r-значение.

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

int obj1; int obj2;

int main() { // ...

int local = obj1 + obj2;

return 0;

выражение, представляющее собой l-значение:

}

Здесь obj1 и obj2 – это l-значения. Однако для выполнения сложения в функции main() из переменных obj1 и obj2 извлекаются их значения. Действие, состоящее в извлечении значения объекта, представленного выражением вида l-значение, называется преобразованием l-значения в r-значение.

Когда функция ожидает аргумент, переданный по значению, то в случае, если аргумент

#include <string>

string color( "purple" ); void print( string );

int main() {

// точное соответствие: преобразование

print( color );

lvalue

// в rvalue

return 0;

 

является l-значением, выполняется его преобразование в r-значение:

}

Так как аргумент в вызове print(color) передается по значению, то производится преобразование l-значения в r-значение для извлечения значения color и передачи его в функцию с прототипом print(string). Однако несмотря на то, что такое приведение

имело место, считается, что фактический аргумент color точно соответствует объявлению print(string).

При вызове функций не всегда требуется применять к аргументам подобное преобразование. Ссылка представляет собой l-значение; если у функции есть параметрссылка, то при вызове функция получает l-значение. Поэтому к фактическому аргументу, которому соответствует формальный параметр-ссылка, описанное преобразование не

#include

<li

st>

применяется. Например, пусть объявлена такая функция: void print( list<int> & );

В вызове ниже li – это l-значение, представляющее объект list<int>, передаваемый

list<int> li(20);

 

int main() {

 

// ...

// точное соответствие: нет преобразования lvalue

print( li );

в

// rvalue

return 0;

 

функции print():

}

Сопоставление li с параметром-ссылкой считается точным соответствием.

Второе преобразование, при котором все же фиксируется точное соответствие, – это преобразование массива в указатель. Как уже отмечалось в разделе 7.3, параметр функции никогда не имеет тип массива, трансформируясь вместо этого в указатель на его первый элемент. Аналогично фактический аргумент типа массива из NT (где N – число элементов в массиве, а T – тип каждого элемента) всегда приводится к типу указателя на T. Такое преобразование типа фактического аргумента и называется преобразованием массива в указатель. Несмотря на это, считается, что фактический аргумент точно

int ai[3];

void putValues(int *);

int main() { // ...

putValues(ai); // точное соответствие: преобразование массива

в

// указатель

return 0;

соответствует формальному параметру типа “указатель на T”. Например:

}

Перед вызовом функции putValues() массив преобразуется в указатель, в результате чего фактический аргумент ai (массив из трех целых) приводится к указателю на int.

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

При установлении точного соответствия допустимо также преобразование функции в указатель. (Оно упоминалось в разделе 7.9.) Как и параметр-массив, параметр-функция становится указателем на функцию. Фактический аргумент типа “функция” также автоматически приводится к типу указателя на функцию. Такое преобразование типа фактического аргумента и называется преобразованием функции в указатель. Хотя трансформация производится, считается, что фактический аргумент точно соответствует

int lexicoCompare( const string &, const string & );

typedef int (*PFI)( const string &, const string & ); void sort( string *, string *, PFI );

string as[10];

int main()

{

// ...

sort( as,

as + sizeof(as)/sizeof(as[0] - 1 ), lexicoCompare // точное соответствие

// преобразование функции в

указатель

);

return 0;

формальному параметру. Например:

}

Перед вызовом sort() применяется преобразование функции в указатель, которое приводит аргумент lexicoCompare от типа “функция” к типу “указатель на функцию”. Хотя формальным параметром функции является указатель, а фактическим – имя функции и, следовательно, было произведено преобразование функции в указатель, считается, что фактический аргумент точно третьему формальному параметру функции sort().

Последнее из перечисленных выше – это преобразование спецификаторов. Оно относится только к указателям и заключается в добавлении спецификаторов const или

int a[5] = { 4454, 7864, 92, 421, 938 }; int *pi = a;

bool is_equal( const int * , const int * );

void func( int *parm ) {

// точное соответствие между pi и parm: преобразование спецификаторов

if ( is_equal( pi, parm ) ) // ...

return 0;

volatile (или обоих) к типу, который адресует данный указатель:

}

Перед вызовом функции is_equal() фактические аргументы pi и parm преобразуются из типа “указатель на int” в тип “указатель на const int”. Эта трансформация заключается в добавлении спецификатора const к адресуемому типу, поэтому относится к категории преобразований спецификаторов. Несмотря на то, что функция ожидает получить два указателя на const int, а фактические аргументы являются указателями на int, считается, что точное соответствие между формальными и фактическими параметрами функции is_equal() установлено.

Преобразование спецификаторов применимо только к типу, который адресует указатель. Оно не употребляется в случае, когда формальный параметр имеет спецификатор const

extern void takeCI( const int );

int main() {

 

int ii = ...;

// преобразование спецификаторов не

takeCI(ii);

применяется return 0;

или volatile, а фактический аргумент – нет.

}

Хотя формальный параметр функции takeCI() имеет тип const int, а вызывается она с аргументом ii типа int, преобразование спецификаторов не производится: есть точное соответствие между фактическим аргументом и формальным параметром.

Все сказанное верно и для случая, когда аргумент является указателем, а спецификаторы

extern void init( int *const ); extern int *pi;

int main() {

 

// ...

// преобразование спецификаторов не

init(pi);

применяется return 0;

const или volatile относятся к этому указателю:

}

Спецификатор const при формальном параметре функции init() относится к самому указателю, а не к типу, который он адресует. Поэтому компилятор при анализе преобразований, которые должны быть применены к фактическому аргументу, не учитывает этот спецификатор. К аргументу pi не применяется преобразование спецификатора: считается, что этот аргумент и формальный параметр точно соответствуют друг другу.

Первые три из рассмотренных преобразований (l-значения в r-значение, массива в указатель и функции в указатель) часто называют трансформациями l-значений. (В разделе 9.4 мы увидим, что хотя и трансформации l-значений, и преобразования спецификаторов относятся к категории преобразований, не нарушающих точного соответствия, его степень считается выше в случае, когда необходима лишь первая трансформация. В следующем разделе мы поговорим об этом несколько подробнее.)

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