2010-09-11 2 views
5

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

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

  2. void foo(int);

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

Очевидно, шаблон специализации существует по причине. Когда его следует использовать? Я прочитал статью Sutter's "Why not Specialize...", но мне нужно больше нестандартной версии, так как я просто изучаю этот материал.

+0

, возможно, адрес: '& foo vs (void (...)) foo' или специализирующий тип возврата? – Anycorn

+0

Возможный дубликат [Специализация специализации важности и необходимости] (http://stackoverflow.com/questions/2197141/function-template-specialization-importance-and-necessiness) – jamesdlin

ответ

11

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

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

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

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 шаблона, последние два вызова вызвали бы эту специализацию, а не общий шаблон.

+1

Хороший вопрос о 'f ' вещь. –

+0

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

+0

Хотя если в вашем интерфейсе указано, что есть функция (-template) 'f', которую вы можете вызывать, тогда вам не разрешено работать с чем-то вроде' f ', и вам это не нужно, потому что шаблон аргумент выводится в любом случае. Расширение стандартной библиотечной функциональности может быть хорошо выполнено ADL (например, с помощью 'std :: swap'), в то время как это часто невозможно сделать, специализируясь на пространстве имен' std' (вы не можете специализировать его для шаблонного аргумента, например - this потребует частичной специализации). Цикл, основанный на диапазоне, полностью основан на ADL. –

1

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

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() будет работать, но может не всегда быть эффективной. В результате я показал, как его можно сделать более эффективным с помощью специфической специализированной специализации.

Мы также можем использовать перегрузку для достижения такого же эффекта.

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

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

+0

Вопрос в том, почему не использовать перегрузку вместо специализации? –

+0

Мартин, я думаю, он спрашивал: зачем предоставлять специализацию, а не перегрузку MySpace() без шаблонов. –

4

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

Herb Sutter написал статью Why not specialize function templates?, где он обескураживает специализированные шаблоны функций в пользу либо их перегрузки, либо их записи, так что они просто пересылают статическую функцию шаблона класса и вместо этого специализируют шаблон шаблона.

+0

Поскольку спецификация шаблонов функций не участвует в процессе перегрузки, это «специальная функция шаблона», потому что она ускоряет разрешение перегрузки – Chubsdad

+0

@Chubsdad: Я бы никогда не попытался оптимизировать время компиляции. Решения должны приниматься на более высоких основаниях, чем время, затрачиваемое компилятором на обработку кода. –

+0

Вы должны попробовать и оптимизировать сам компилятор :) –

0

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

+0

Виртуальный метод не может быть шаблоном. – Potatoswatter

+0

@Potato: Я думаю, он рисовал параллель. Если вы объявляете функцию виртуальной, но не производные классы перегружаете ее, виртуальный вид идет впустую. Конечно, это не очень хорошая параллель. –

-1

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

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

Это выглядит не так. 'stdout' - это объект (и ему разрешено быть макросом, который в значительной степени разрушает все), поэтому он не должен соответствовать типу аргументов' class Os'. В любом случае вам нужно «template < FILE * > std :: ostream & get();' – Potatoswatter

+0

@Potatoswatter: его внутреннее (внутреннее пространство имен). Позже он вводится в общедоступное (не глобальное) пространство имен библиотеки под собственным именем. шаблон хороший тоже. – Daniel

0

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

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

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

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

+0

Ну, формальная перегрузка действительно происходит только в одной области. Если две объявления функций находятся в разных областях, они не перегружают друг друга. Но фактический момент, который я хочу сделать, - представьте себе, что у вас есть шаблон template Container {...}; '- как вы хотите специализировать' std :: swap' для работы над этим контейнером? Это невозможно, и вы, вероятно, закончите делать это с помощью ADL. По этой причине универсальные функции не просто вызовут 'std :: swap', потому что это будет совершенно неутешительное качество реализации. –

+1

Я думаю, что общий шаблон - это написать '{using std :: swap; своп (a, b); } ', который находит как объявления ADL, так и специализации' std :: swap'. Мне легче перегружать. Каждый должен сделать это, как он считает нужным :) –

+0

@Johannes: У вас есть работоспособное решение, но это не то, что указывает стандарт. Дело в том, чтобы реализовать 'swap' для класса, так что он найден на' '. (Да, для поиска соответствия используется только «лучшая» область ... но также и источник разочарования! Там есть явная инверсия приоритета, так как пользователь, вероятно, не увидит конкретные детали как наиболее важный фактор.) – Potatoswatter

Смежные вопросы