C ++ - Какова цель специализации шаблонов функций?Когда его использовать? - PullRequest
5 голосов
/ 12 сентября 2010

Изучая C ++, натолкнулся на шаблоны функций.В главе упоминается шаблон специализации.

  1. template <> void foo<int>(int);

  2. void foo( int );

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

Очевидно, что специализация шаблона существует по причине.Когда его следует использовать?Я прочитал статью Саттера "Почему бы не специализироваться ..." , но мне нужно больше версии для непрофессионала, так как я только изучаю этот материал.

Ответы [ 6 ]

12 голосов
/ 12 сентября 2010

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

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

С другой стороны, если в любом месте вы предоставляете аргумент шаблона (вместо того, чтобы позволить выводить компилятор), он просто вызовет универсальный шаблон и, вероятно, даст неожиданные результаты:

template <typename T> void f(T) { 
   std::cout << "generic" << std::endl; 
}
void f(int) { 
   std::cout << "f(int)" << std::endl; 
}
int main() {
   int x = 0;
   double d = 0.0;
   f(d); // generic
   f(x); // f(int)
   f<int>(x); // generic !! maybe not what you want
   f<int>(d); // generic (same as above)
}

Если бы вы указали специализацию для int шаблона, последние два вызова вызвали бы эту специализацию, а не универсальный шаблон.

4 голосов
/ 12 сентября 2010

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

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

1 голос
/ 12 сентября 2010

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

template<typename T>
void MySwap(T& lhs, T& rhs)
{
    T tmp(lhs);
    lhs  = rhs;
    rhs  = tmp;
}

Теперь для векторов мой обмен будет работать, но он не очень эффективен.Но я также знаю, что std :: vector реализует свой собственный метод swap ().

template<>
void MySwap(std::vector<int>& lhs,std::vector<int>& rhs)
{
    lhs.swap(rhs);
}

Пожалуйста, не сравнивайте с std :: swap, который намного сложнее и лучше написан.Это всего лишь пример, показывающий, что универсальная версия MySwap () будет работать, но не всегда может быть эффективной.В результате я показал, как его можно сделать более эффективным с очень специфической специализацией шаблонов.

Конечно, мы также можем использовать перегрузку для достижения того же эффекта.вопрос, зачем использовать специализацию шаблона (если можно использовать перегрузку). Почему так и есть .Не шаблонная функция всегда будет выбираться поверх шаблонной функции.Таким образом, правила специализации шаблонов даже не вызываются (что делает жизнь намного проще, поскольку эти правила причудливы, если вы не юрист, а программист).Итак, позвольте мне подумать секунду. Нет, не могу придумать вескую причину .

0 голосов
/ 12 сентября 2010

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

Например, если вы расширяете std::swap для поддержки вашего типа, вы должны сделать это по специализации, потому что функция называется std::swap, а не просто swap, и функции в <algorithm> будут совершенно правильными специально назвать его как ::std::swap( a, b );. Аналогично для любого имени, которое может быть наложено псевдонимом в пространствах имен: вызов функции может усложниться после того, как вы определите имя.

Проблема с областью видимости еще более усложняется из-за аргумент-зависимого поиска . Часто перегрузка может быть найдена, потому что она определена в непосредственной близости от типа одного из его аргументов. (Например, в качестве статической функции-члена.) Это полностью отличается от того, как будет найдена специализация шаблона, а именно путем простого поиска имени шаблона и последующего поиска явной специализации, как только шаблон будет выбран в качестве цели. вызова.

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

0 голосов
/ 12 сентября 2010

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

namespace internal{namespace os{
    template <class Os> std::ostream& get();

    struct stdout{};
    struct stderr{};

    template <> inline std::ostream& get<stdout>() { return std::cout; }
    template <> inline std::ostream& get<stderr>() { return std::cerr; }
}}

// define a specialization for os::get()
#define DEFINE_FILE(ofs_name,filename)\
    namespace internal{namespace os{                        \
        struct ofs_name{                                    \
            std::ofstream ofs;                              \
            ofs_name(){ ofs.open(filename);}                        \
            ~ofs_name(){ ofs.close(); delete this; }                    \
        };                                          \
        template <> inline std::ostream& get<ofs_name>(){ return (new ofs_name())->ofs; }   \
    }}                                              \
    using internal::os::ofs_name;   
0 голосов
/ 12 сентября 2010

Я считаю это очень важным.Вы можете использовать это так же, как виртуальный метод.Не было бы никакого смысла в виртуальных методах, если бы некоторые из них не были специализированными.Я много использовал его, чтобы различать простые типы (int, short, float) и объекты, указатели объектов и ссылки на объекты.В качестве примера можно привести методы сериализации / десериализации, которые будут обрабатывать объекты путем вызова метода сериализации / десериализации объектов, тогда как простые типы должны записываться непосредственно в поток.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...