<< Пред.           стр. 44 (из 121)           След. >>

Список литературы по разделу

 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 oper();
  void print( oper() ); // вызов функции
 
 (e) void print( const int );
  int iobj;
  print( iobj ); // вызов функции
 9.4. Детали разрешения перегрузки функций
 В разделе 9.2 мы уже упоминали, что процесс разрешения перегрузки функций состоит из трех шагов:
 Установить множество функций-кандидатов для разрешения данного вызова, а также свойства списка фактических аргументов.
 Отобрать из множества кандидатов устоявшие функции – те, которые могут быть вызваны с данным списком фактических аргументов при учете их числа и типов.
 Выбрать функцию, лучше всего соответствующую вызову, подвергнув ранжированию преобразования, которые необходимо применить к фактическим аргументам, чтобы привести их в соответствие с формальными параметрами устоявшей функции.
 Теперь мы готовы к тому, чтобы изучить эти шаги более детально.
 9.4.1. Функции-кандидаты
 Функцией-кандидатом называется функция, имеющая то же имя, что и вызванная. Кандидаты отыскиваются двумя способами:
 объявление функции видимо в точке вызова. В следующем примере
 void f();
 void f( int );
 void f( double, double = 3.4 );
 void f( char*, char* );
 
 int main() {
  f( 5.6 ); // для разрешения этого вызова есть четыре кандидата
  return 0;
 }
 все четыре функции f() удовлетворяют этому условию. Поэтому множество кандидатов содержит четыре элемента;
 если тип фактического аргумента объявлен внутри некоторого пространства имен, то функции-члены этого пространства, имеющие то же имя, что и вызванная функция, добавляются в множество кандидатов:
 namespace NS {
  class C { /* ... */ };
  void takeC( C& );
 }
 
 // тип cobj - это класс C, объявленный в пространстве имен NS
 NS::C obj;
 
 int main() {
  // в точке вызова не видна ни одна из функций takeC()
  takeC( cobj); // правильно: вызывается NS::takeC( C& ),
  // потому что аргумент имеет тип NS::C, следовательно,
  // принимается во внимание функция takeC(),
  // объявленная в пространстве имен NS
  return 0;
 }
 Таким образом, совокупность кандидатов является объединением множества функций, видимых в точке вызова, и множества функций, объявленных в том же пространстве имен, к которому принадлежат типы фактических аргументов.
 При идентификации множества перегруженных функций, видимых в точке вызова, применимы уже рассмотренные ранее правила.
 Функция, объявленная во вложенной области видимости, скрывает, а не перегружает одноименную функцию во внешней области. В такой ситуации кандидатами будут только функции из во вложенной области, т.е. такие, которые не скрыты при вызове. В следующем примере функциями-кандидатами, видимыми в точке вызова, являются format(double) и format(char*):
 char* format( int );
 void g() {
  char *format( double );
  char* format( char* );
 
  format(3); // вызывается format( double )
 }
 Так как format(int), объявленная в глобальной области видимости, скрыта, она не включается в множество функций-кандидатов.
 Кандидаты могут быть введены с помощью using-объявлений, видимых в точке вызова:
 namespace libs_R_us {
  int max( int, int );
  double max( double, double );
 }
 
 char max( char, char );
 
 void func()
 {
  // функции из пространства имен невидимы
  // все три вызова разрешаются в пользу глобальной функции max( char, char )
  max( 87, 65 );
  max( 35.5, 76.6 );
  max( 'J', 'L' );
 }
 Функции max(), определенные в пространстве имен libs_R_us, невидимы в точке вызова. Единственной видимой является функция max() из глобальной области; только она входит в множество функций-кандидатов и вызывается при каждом из трех обращений к func(). Мы можем воспользоваться using-объявлением, чтобы сделать видимыми функции max() из пространства имен libs_R_us. Куда поместить using-объявление? Если включить его в глобальную область видимости:
 char max( char, char );
 using libs_R_us::max; // using-объявление
 то функции max() из libs_R_us добавляются в множество перегруженных функций, которое уже содержит max(), объявленную в глобальной области. Теперь все три функции видны внутри func() и становятся кандидатами. В этой ситуации вызовы func() разрешаются следующим образом:
 void func()
 {
  max( 87, 65 ); // вызывается libs_R_us::max( int, int )
  max( 35.5, 76.6 ); // вызывается libs_R_us::max( double, double )
  max( 'J', 'L' ); // вызывается ::max( char, char )
 }
 Но что будет, если мы введем using-объявление в локальную область видимости функции func(), как показано в данном примере?
 void func()
 {
  // using-объявление
  using libs_R_us::max;
 
  // те же вызовы функций, что и выше
 }
 Какие из функций max() будут включены в множество кандидатов? Напомним, что using-объявления вкладываются друг в друга. При наличии такого объявления в локальной области глобальная функция max(char, char) оказывается скрытой, так что в точке вызова видны только
 libs_R_us::max( int, int );
 libs_R_us::max( double, double );
 Они и являются кандидатами. Теперь вызовы func() разрешаются следующим образом:
 void func()
 {
  // using-объявление
  // глобальная функция max( char, char ) скрыта
  using libs_R_us::max;
 
  max( 87, 65 ); // вызывается libs_R_us::max( int, int )
  max( 35.5, 76.6 ); // вызывается libs_R_us::max( double, double )
  max( 'J', 'L' ); // вызывается libs_R_us::max( int, int )
 }
 Using-директивы также оказывают влияние на состав множества функций-кандидатов. Предположим, мы решили их использовать, чтобы сделать функции max() из пространства имен libs_R_us видимыми в func(). Если разместить следующую using-директиву в глобальной области видимости, то множество функций-кандидатов будет состоять из глобальной функции max(char, char) и функций max(int, int) и max(double, double), объявленных в libs_R_us:
 namespace libs_R_us {
  int max( int, int );
  double max( double, double );
 }
 
 char max( char, char );
 using namespace libs_R_us; // using-директива
 
 void func()
 {
  max( 87, 65 ); // вызывается libs_R_us::max( int, int )
  max( 35.5, 76.6 ); // вызывается libs_R_us::max( double, double )
  max( 'J', 'L' ); // вызывается ::max( int, int )
 }
 Что будет, если поместить using-директиву в локальную область видимости, как в следующем примере?
 void func()
 {
  // using-директива
  using namespace libs_R_us;
 
  // те же вызовы функций, что и выше
 }
 Какие из функций max() окажутся среди кандидатов? Напомним, что using-директива делает члены пространства имен видимыми, словно они были объявлены вне этого пространства, в той точке, где такая директива помещается. В нашем примере члены libs_R_us видимы в локальной области функции func(), как будто они объявлены вне пространства – в глобальной области. Отсюда следует, что множество перегруженных функций, видимых внутри func(), то же, что и раньше, т.е. включает в себя
 max( char, char );
 libs_R_us::max( int, int );
 libs_R_us::max( double, double );
 В локальной или глобальной области видимости появляется using-директива, на разрешение вызовов функции func() не влияет:
 void func()
 {
  using namespace libs_R_us;
 
  max( 87, 65 ); // вызывается libs_R_us::max( int, int )
  max( 35.5, 76.6 ); // вызывается libs_R_us::max( double, double )
  max( 'J', 'L' ); // вызывается ::max( int, int )
 }
 Итак, множество кандидатов состоит из функций, видимых в точке вызова, включая и те, которые введены using-объявлениями и using-директивами, а также из функций, объявленных в пространствах имен, ассоциированных с типами фактических аргументов. Например:
 namespace basicLib {
  int print( int );
  double print( double );
 }
 namespace matrixLib {
  class matrix { /* ... */ };
  void print( const maxtrix & );
 }
 void display()
 {
  using basicLib::print;
 
  matrixLib::matrix mObj;
  print( mObj ); // вызывается maxtrixLib::print( const maxtrix & )
 
  print( 87 ); // вызывается basicLib::print( const maxtrix & )
 }
 Кандидатами для print(mObj) являются введенные using-объявлением внутри display() функции basicLib::print(int) и basicLib::print(double), поскольку они видимы в точке вызова. Так как фактический аргумент функции имеет тип matrixLib::matrix, то функция print(), объявленная в пространстве имен matrixLib, также будет кандидатом. Каковы функции-кандидаты для print(87)? Только basicLib::print(int) и basicLib::print(double), видимые в точке вызова. Поскольку аргумент имеет тип int, дополнительное пространство имен в поисках других кандидатов не рассматривается.
 9.4.2. Устоявшие функции
 Устоявшая функция относится к числу кандидатов. В списке ее формальных параметров либо то же самое число элементов, что и в списке фактических аргументов вызванной функции, либо больше. В последнем случае для дополнительных параметров задаются значения по умолчанию, иначе функцию нельзя будет вызвать с данным числом аргументов. Чтобы функция считалась устоявшей, должно существовать преобразование каждого фактического аргумента в тип соответствующего формального параметра. (Такие преобразования были рассмотрены в разделе 9.3.)
 В следующем примере для вызова f(5.6) есть две устоявшие функции: f(int) и f(double).
 void f();
 void f( int );
 void f( double );
 void f( char*, char* );
 
 int main() {
  f( 5.6 ); // 2 устоявшие функции: f( int ) и f( double )
  return 0;
 }
 Функция f(int) устояла, так как она имеет всего один формальный параметр, что соответствует числу фактических аргументов в вызове. Кроме того, существует стандартное преобразование аргумента типа double в int. Функция f(double) также устояла; она тоже имеет один параметр типа double, и он точно соответствует фактическому аргументу. Функции-кандидаты f() и f(char*, char*) исключены из списка устоявших, так как они не могут быть вызваны с одним аргументом.
 В следующем примере единственной устоявшей функцией для вызова format(3) является format(double). Хотя кандидата format(char*) можно вызывать с одним аргументом, не существует преобразования из типа фактического аргумента int в тип формального параметра char*, а следовательно, функция не может считаться устоявшей.
 char* format( int );
 void g() {
  // глобальная функция format( int ) скрыта
  char* format( double );
  char* format( char* );
  format(3); // есть только одна устоявшая функция: format( double )
 }
 В следующем примере все три функции-кандидата оказываются устоявшими для вызова max() внутри func(). Все они могут быть вызваны с двумя аргументами. Поскольку фактические аргументы имеют тип int, они точно соответствуют формальным параметрам функции libs_R_us::max(int, int) и могут быть приведены к типам параметров функции libs_R_us::max(double, double) с помощью трансформации целых в плавающие, а также к типам параметров функции libs_R_us::max(char, char) посредством преобразования целых типов.
 namespace libs_R_us {
  int max( int, int );
  double max( double, double );
 }
 
 // using-объявление
 using libs_R_us::max;
 
 char max( char, char );
 void func()
 {
  // все три функции max() являются устоявшими
  max( 87, 65 ); // вызывается using libs_R_us::max( int, int )
 }
 Обратите внимание, что функция-кандидат с несколькими параметрами исключается из числа устоявших, как только выясняется, что один из фактических аргументов не может быть приведен к типу соответствующего формального параметра, пусть даже для всех остальных аргументов такое преобразование существует. В следующем примере функция min(char *, int) исключается из множества устоявших, поскольку нет возможности трансформации типа первого аргумента int в тип соответствующего параметра char *. И это происходит несмотря на то, что второй аргумент точно соответствует второму параметру.
 extern double min( double, double );
 extern double min( char*, int );
 
 void func()
 {
  // одна функция-кандидат min( double, double )
  min( 87, 65 ); // вызывается min( double, double )
 }
 Если после исключения из множества кандидатов всех функций с несоответствующим числом параметров и тех, для параметров которых не оказалось подходящего преобразования, не осталось устоявших, то обработка вызова функции заканчивается ошибкой компиляции. В таком случае говорят, что соответствия не найдено.
 void print( unsigned int );
 void print( char* );
 void print( char );
 
 int *ip;
 class SmallInt { /* ... */ };
 SmallInt si;
 
 int main() {
  print( ip ); // ошибка: нет устоявших функций: соответствие не найдено
  print( si ); // ошибка: нет устоявших функций: соответствие не найдено
  return 0;
 }
 9.4.3. Наилучшая из устоявших функция
 Наилучшей считается та из устоявших функций, формальные параметры которой наиболее точно соответствуют типам фактических аргументов. Для любой такой функции преобразования типов, применяемые к каждому аргументу, ранжируются для определения степени его соответствия параметру. (В разделе 6.2 описаны поддерживаемые преобразования типов.) Наилучшей из устоявших называют функцию, для которой одновременно выполняются два условия:
 преобразования, примененные к аргументам, не хуже преобразований, необходимых для вызова любой другой устоявшей функции;
 хотя бы для одного аргумента примененное преобразование лучше, чем для того же аргумента в любой другой устоявшей функции.
 Может оказаться так, что для приведения фактического аргумента к типу соответствующего формального параметра нужно выполнить несколько преобразований. Так, в следующем примере
 int arr[3];
 void putValues(const int *);
 
 int main() {
  putValues(arr); // необходимо 2 преобразования
  // массив в указатель + преобразование спецификатора
  return 0;
 }
 для приведения аргумента arr от типа “массив из трех int” к типу “указатель на const int” применяется последовательность преобразований:
 Преобразование массива в указатель, которое трансформирует массив из трех int в указатель на int.
 Преобразование спецификатора, которое трансформирует указатель на int в указатель на const int.
 Поэтому было бы более правильно говорить, что для приведения фактического аргумента к типу формального параметра устоявшей функции требуется последовательность преобразований. Поскольку применяется не одна, а несколько трансформаций, то на третьем шаге процесса разрешения перегрузки функции на самом деле ранжируются последовательности преобразований.
 Рангом такой последовательности считается ранг самой плохой из входящих в нее трансформаций. Как объяснялось в разделе 9.2, преобразования типов ранжируются следующим образом: точное соответствие лучше расширения типа, а расширение типа лучше стандартного преобразования. В предыдущем примере оба изменения имеют ранг точного соответствия. Поэтому и у всей последовательности такой же ранг.
 Такая совокупность состоит из нескольких преобразований, применяемых в указанном порядке:
 преобразование l-значения ->
  расширение типа или стандартное преобразование ->
  преобразование спецификаторов
 Термин преобразование l-значения относится к первым трем трансформациям из категории точных соответствий, рассмотренных в разделе 9.2: преобразование l-значения в r-значение, преобразование массива в указатель и преобразование функции в указатель. Последовательность трансформаций состоит из нуля или одного преобразования l-значения, за которым следует нуль или одно расширение типа или стандартное преобразование, и наконец нуль или одно преобразование спецификаторов. Для приведения фактического аргумента к типу формального параметра может быть применено только одна трансформация каждого вида.
 Описанная последовательность называется последовательностью стандартных преобразований. Существует также последовательность определенных пользователем преобразований, которая связана с функцией-конвертером, являющейся членом класса. (Конвертеры и последовательности определенных пользователем преобразований рассматриваются в главе 15.)
 Каковы последовательности изменений фактических аргументов в следующем примере?
 namespace libs_R_us {
  int max( int, int );
  double max( double, double );

<< Пред.           стр. 44 (из 121)           След. >>

Список литературы по разделу