Перегрузка и специализация. Тонкий момент

    В сегодняшней краткой заметке я опишу тонкий момент связанный с перегрузкой и специализацией функций. Не так давно встретилось на практике и появился повод проапдейтить запись в личной БД на эту тему. Этой информацией и поделюсь.

    Пример


    template<typename T> void foo(T);  // 1
    template<> void foo(int*);         // 2
    template<typename T> void foo(T*); // 3
    
    template<typename T> void bar(T);  // 4
    template<typename T> void bar(T*); // 5
    template<> void bar(int*);         // 6
    
    void f() {
    	int i;
    	foo(&i);
    	bar(&i);
    }
    

    Итак у нас есть 2 абсолютно одинаковых набора функций, однако, как вы понимаете, без подвоха тут не обойдется. Какие именно из них будут вызваны?

    Объяснение


    Те кто ответил 3 и 6 могут сегодня потратить на торчание на хабре на полчаса больше рабочего времени чем обычно. Чтобы объяснить такое поведение нужно вспомнить какие существуют категории функций с точки зрения перегрузки.

    1. Обычные функции;
    2. базовые шаблоны. Это функции вида
    template<typename T> void bar(T);
    

    3. Специализации шаблонов функций.

    Поведение компилятора для разрешения перегрузки между этими тремя категориями вполне ожидаемо. Сначала выбирается лучший кандидат среди обычных функций и базовых шаблонов, причем предпочтение отдается обычным функциям. Если же более подходящим является базовый шаблон, то проверяется а нет ли у него еще более подходящих специализаций и, если есть, выбирается одна из них.

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

    То есть в примере выше (2) является специализацией не (3) а (1), поэтому для перегрузки будет выбран более подходящий базовый шаблон (3). Во втором же случае (6) является специализацией (5), которая в свою очередь является лучшей кандидатурой, чем (4).

    Похожие публикации

    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 19

      0
      То есть в примере выше (2) является специализацией не (3) а (1), поэтому для перегрузки будет выбран более подходящий базовый шаблон (3). Во втором же случае (6) является специализацией (5), которая в свою очередь является лучшей кандидатурой, чем (4).

      Вот тут-то секрета никакого нет. подобное поведение подобно ситуации
      fun{
       x=0;
      double x;
      }
      

      Причина — однопроходность компилятора. А вот то, что * — это спецификатор, вот про здесь я забыл, видать надо в коде писать не int* x, а int *x
        0
        Внимание, вопрос.
        template<class T> void foo(T ) {...}  // первый базовый шаблон
        template<class T> void foo(T*) {...} // второй базовый шаблон
        
        template<> void foo(int*) {...} // специализация второго шаблона
        
        // как сделать специализацию первого шаблона для int* ?
        

          0
          Скорее всего придется функции разнести в разные заголовочные файлы, а специализации делать с в соответсвующих cpp файлах.
            +2
            Специализация
            template<> void foo(int*) { ... }

            одновременно является и специализацией первого шаблона с T=int*, и второго с T=int

            ИМХО, вопрос не имеет смысла.
              0
              с чего бы?
                +1
                Чтобы имело смысл делать две разные специализации, надо иметь возможность их по-разному вызвать.
                Этот код вызовет ф-цию, помеченную комментарием «специализация второго шаблона»
                int *x = new int;
                foo(x);


                Как сделать вызов «специализиации первого шаблона для int*»?
                  +1
                  foo<int*>(x) очевидно же
                    0
                    Спасибо. Настолько привык, что компилятор сам выводит тип, что позабыл о возможности ручного указания.
                0
                А если мы хотим отдельно специализировать первый шаблон, и отдельно — другим способом — второй?

                У нас не пролог и не хаскелл, где в языке есть требование — паттерны одной функции должны идти подряд, и если кто не всунул специализацию первого шаблона перед объявлением второго, то сам виноват.
                  0
                  Если компилятор обнаружит две специализации одного и тогоже шаблона, то будет ошибка компиляции или линковки
                    0
                    А если компилятор обнаружит две специализации двух разных шаблонов, то ошибки не будет.

                    Вот смотрите
                    #include <cstdio>
                    
                    #define SHOW(i) printf("%d : %s \n", i, __PRETTY_FUNCTION__)
                    
                    template<class T> void foo(T) { SHOW(1); } // это - главное определение первого шаблона
                    template<> void foo(char*); // объявляем
                    
                    auto fi = foo<int*>;
                    auto fc = foo<char*>;
                    auto fs = foo<short*>;
                    
                    //template<> void foo(char*) { SHOW(2); } // это определение специализации первого шаблона
                    
                    
                    template<class T> void foo(T*) { SHOW(3); } // это - главное определение второго шаблона
                    template<> void foo(char*) { SHOW(4); } // это специализация ТОЛЬКО второго шаблона
                    template<> void foo(int*) { SHOW(5); }
                    
                    template<class T> void bar(void(*p)(T*))
                    {
                    	p(0);
                    }
                    
                    int main()
                    {
                    	fi(0); // 1
                    	fc(0); // 2
                    	fs(0); // 1
                    
                    	bar<int>(foo); // 5
                    	bar<char>(foo); // 4
                    	bar<short>(foo); // 3
                    }
                    

                    Если закомментировать специализацию первого шаблона, линкер выругается. Потому что вторая специализация (SHOW(4)) — не относится к первому шаблону. Потому что второй шаблон замаскировал наличие первого, начиная с точки своего объявления.
                    А если раскомментировать — всё будет так, как задумано. Но определение должно находиться выше объявления второго шаблона, в этом беда.
                      0
                      Потому что второй шаблон замаскировал наличие первого, начиная с точки своего объявления.

                      Он не замаскировал, он приобрел больший приоритет, как лучше соответствующий.

                      С шаблонами вообще интересно. В приведенном коде поменям:

                      template<class T> void foo(T) { SHOW(1); } // это - главное определение первого шаблона
                      auto fc = foo<char*>;
                      template<> void foo(char*); // объявляем
                      
                      auto fi = foo<int*>;
                      auto fs = foo<short*>;
                      

                      Получим ошибку компиляцию.

                      Весь прикол в том, что шаблон словно кот Шредингенра, вроде он есть, а вроде его и нет. Однозначно он начинает существовать только после инстацирования.

                      Благодаря этому и появляется возможность специализировать и даже частично специализировать или при специализации добавлять новые шаблонные параметры. Ну и т.п.
                        0
                        Резюмируя: специализации шаблонов — это редкостный тёмный лес, и проще туда попросту не соваться, чем соваться, огребать и разгребать.
                0
                Правильный ответ на ваш вопрос — не надо ) Не надо писать специализацию для первого шаблона. Если вы хотите написать что-то специфическое для int*, напишите
                void foo(int*){}
                

                Общее правило — не пишите специализацию, если можно решить проблему перегрузкой.
                  0
                  Переместить третью непустую строчку на позицию после первой.
                  +1
                  Те кто ответил 3 и 6 могут сегодня потратить на торчание на хабре на полчаса больше рабочего времени чем обычно.

                  Что-то я запутался, может, наоборот — «меньше»? Ведь, не нужно читать статью до конца. Или — время не потраченное на эту статью — можно потратить на какую-то другую? Но тогда, время «торчания» на хабре не изменится? оО
                    +1
                    Скорее всего предполагалось, что 30 минут будет проведено за книжками\доками в поисках причины такого поведения.
                      0
                      Имелось в виду, что раз все знаешь, то рабочий процесс не пострадает от того, что на него будет потрачено на полчаса меньше времени, легко наверстаешь, а если нет, то иди работай лучше, а то не успеешь )
                      Наверное, неясно выразил свою мысль.
                      +1
                      Кстати, можете добавить более развёрнутые размышления на эту тему от Sutter-а: Why Not Specialize Function Templates?. Там хорошо разжёвано. Например, указано, что в случае (3) — мы имеем перегрузку функции (second base template, overloads (1)) и что явные специализации не перегружают (Specializations don't overload), отсюда и всё вытекает.

                      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                      Самое читаемое