Бьерн Страуструп - Язык программирования С++. Главы 8-10 - Разрешение перегрузки для шаблонной функции

ОГЛАВЛЕНИЕ


8.5 Разрешение перегрузки для шаблонной функции

К параметрам шаблонной функции нельзя применять никаких преобразований типа. Вместо этого при необходимости создаются новые варианты функции:
            template<class T> T sqrt(t);

            void f(int i, double d, complex z)
            {
              complex z1 = sqrt(i);  // sqrt(int)
              complex z2 = sqrt(d);  // sqrt(double)
              complex z3 = sqrt(z);  // sqrt(complex)
              // ...
            }
Здесь для всех трех типов параметров будет создаваться по шаблону своя функция sqrt. Если пользователь захочет чего-нибудь иного, например вызвать sqrt(double), задавая параметр int, нужно использовать явное преобразование типа:
           template<class T> T sqrt(T);

           void f(int i, double d, complex z)
           {
             complex z1 = sqrt(double(i));  // sqrt(double)
             complex z2 = sqrt(d);  // sqrt(double)
             complex z3 = sqrt(z);  // sqrt(complex)
             // ...
           }
В этом примере по шаблону будут создаваться определения только для sqrt(double) и sqrt(complex).

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

      [1] Найти функцию с точным сопоставлением параметров ($$R.13.2); если такая есть, вызвать ее.
      [2] Найти шаблон типа, по которому можно создать вызываемую функцию с точным сопоставлением параметров; если такая есть, вызвать ее.
      [3] Попробовать правила разрешения для обычных функций ($$r13.2); если функция найдена по этим правилам, вызвать ее, иначе вызов является ошибкой.

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

          template<class T>
            T max(T a, T b) { return a>b?a:b; };

          void f(int a, int b, char c, char d)
          {
            int m1 = max(a,b);  // max(int,int)
            char m2 = max(c,d); // max(char,char)
            int m3 = max(a,c);  // ошибка: невозможно
                                // создать max(int,char)
          }
Поскольку до генерации функции по шаблону не применяется никаких преобразований типа (правило [2]), последний вызов в этом примере нельзя разрешить как max(a,int(c)). Это может сделать сам пользователь, явно описав функцию max(int,int). Тогда вступает в силу правило [3]:
          template<class T>
             T max(T a, T b) { return a>b?a:b; }

          int max(int,int);

          void f(int a, int b, char c, char d)
          {
            int m1 = max(a,b);     // max(int,int)
            char m2 = max(c,d);    // max(char,char)
            int m3 = max(a,c);     // max(int,int)
          }
Программисту не нужно давать определение функции max(int,int), оно по умолчанию будет создано по шаблону.

Можно определить шаблон max так, чтобы сработал первоначальный вариант нашего примера:

          template<class T1, class T2>
             T1 max(T1 a, T2 b) { return a>b?a:b; };

          void f(int a, int b, char c, char d)
          {
            int m1 = max(a,b);    // int max(int,int)
            char m2 = max(c,d);   // char max(char,char)
            int m3 = max(a,c);    // max(int,char)
          }
Однако, в С и С++ правила для встроенных типов и операций над ними таковы, что использовать подобный шаблон с двумя параметрами может быть совсем непросто. Так, может оказаться неверно задавать тип результата функции как первый параметр (T1), или, по крайней мере, это может привести к неожиданному результату, например для вызова
          max(c,i);  // char max(char,int)
Если в шаблоне для функции, которая может иметь множество параметров с различными арифметическими типами, используются два параметра, то в результате по шаблону будет порождаться слишком большое число определений разных функций. Более разумно добиваться преобразования типа, явно описав функцию с нужными типами.