2010-03-08 1 views
11

Как и большинство программистов на С ++, должно быть известно, что частичная спецификация шаблонов свободных функций запрещена. Например, следующее является незаконным C++:Частная специализация по шаблонам бесплатных функций - лучшие практики

template <class T, int N> 
T mul(const T& x) { return x * N; } 

template <class T> 
T mul<T, 0>(const T& x) { return T(0); } 

// error: function template partial specialization ‘mul<T, 0>’ is not allowed 

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

template <class T, int N> 
struct mul_impl 
{ 
    static T fun(const T& x) { return x * N; } 
}; 

template <class T> 
struct mul_impl<T, 0> 
{ 
    static T fun(const T& x) { return T(0); } 
}; 

template <class T, int N> 
T mul(const T& x) 
{ 
    return mul_impl<T, N>::fun(x); 
} 

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


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

+2

Я думаю, что это зависит. В вашем случае вы называете это «fun (u)», поэтому вы не можете перегружать ('N' не появляется нигде в параметре). Но я думаю, что если «перегрузка» возможна, это предпочтительный способ. Отличным примером является 'std :: swap' или' std :: begin' или 'std :: end' (где последние два являются функциями C++ 0x). Обратите внимание, что статья Саттера была написана 9 лет назад. Не уверен, что он по-прежнему рекомендует сделать это «делегировать классу». И я думаю, что он не масштабируется красиво: не будет работать с ADL - вам придется возиться со всеми типами пространств имен и специализироваться на их шаблонах. Ничего хорошего –

ответ

3

Как LITB говорит, ADL превосходит, где он может работать, которая в основном всякий раз, когда параметры шаблона могут быть выведены из параметров вызова:

#include <iostream> 

namespace arithmetic { 
    template <class T, class S> 
    T mul(const T& x, const S& y) { return x * y; } 
} 

namespace ns { 
    class Identity {}; 

    // this is how we write a special mul 
    template <class T> 
    T mul(const T& x, const Identity&) { 
     std::cout << "ADL works!\n"; 
     return x; 
    } 

    // this is just for illustration, so that the default mul compiles 
    int operator*(int x, const Identity&) { 
     std::cout << "No ADL!\n"; 
     return x; 
    } 
} 

int main() { 
    using arithmetic::mul; 
    std::cout << mul(3, ns::Identity()) << "\n"; 
    std::cout << arithmetic::mul(5, ns::Identity()); 
} 

Выход:

ADL works! 
3 
No ADL! 
5 

Перегрузка + ADL достигает того, чего вы бы достигли, частично специализировав шаблон функции arithmetic::mul для S = ns::Identity. Но он полагается на вызывающего абонента, чтобы называть его таким образом, который позволяет ADL, поэтому вы никогда не вызываете std::swap явно.

Итак, вопрос в том, чего вы ожидаете от пользователей вашей библиотеки, чтобы частично специализировать свои шаблоны функций? Если они собираются специализировать их для типов (как это обычно бывает с шаблонами алгоритмов), используйте ADL. Если они собираются специализировать их для целочисленных параметров шаблона, как в вашем примере, то, я думаю, вам нужно делегировать класс. Но обычно я не ожидаю, что третья сторона определит, что делать умножение на 3 - моя библиотека будет делать все целые числа. Я мог бы разумно ожидать, что третье лицо определит, что будет делать умножение на octonion.

Давайте думать об этом, экспоненцирование могло бы стать лучшим примером для меня использовать, так как мой arithmetic::mul является похожим на operator*, так что нет никакой фактической необходимости специализироваться mul в моем примере. Затем я бы специализировал/ADL-перегрузку для первого параметра, так как «Идентичность в силе чего-либо - это Identity». Надеюсь, вы поняли эту идею.

Я думаю, что есть недостаток ADL - он эффективно выравнивает пространства имен. Если я хочу использовать ADL для «реализации» arithmetic::sub и sandwich::sub для моего класса, то у меня могут быть проблемы. Я не знаю, что эксперты должны сказать об этом.

я имею в виду:

namespace arithmetic { 
    // subtraction, returns the difference of lhs and rhs 
    template<typename T> 
    const T sub(const T&lhs, const T&rhs) { return lhs - rhs; } 
} 

namespace sandwich { 
    // sandwich factory, returns a baguette containing lhs and rhs 
    template<typename SandwichFilling> 
    const Baguette sub(const SandwichFilling&lhs, const SandwichFilling&rhs) { 
     // does something or other 
    } 
} 

Теперь у меня есть тип ns::HeapOfHam. Я хочу воспользоваться станд :: своп стиле ADL, чтобы написать свою собственную реализацию арифметической :: подразделам:

namespace ns { 
    HeapOfHam sub(const HeapOfHam &lhs, const HeapOfHam &rhs) { 
     assert(lhs.size >= rhs.size && "No such thing as negative ham!"); 
     return HeapOfHam(lhs.size - rhs.size); 
    } 
} 

Я также хочу воспользоваться станд :: своп стиле ADL, чтобы написать свою собственную реализацию сэндвич :: sub:

namespace ns { 
    const sandwich::Baguette sub(const HeapOfHam &lhs, const HeapOfHam &rhs) { 
     // create a baguette, and put *two* heaps of ham in it, more efficiently 
     // than the default implementation could because of some special 
     // property of heaps of ham. 
    } 
} 

Подвешивание на минуту. Я не могу этого сделать, не так ли? Две разные функции в разных пространствах имен с одинаковыми параметрами и разными типами возврата: обычно это не проблема, для чего существуют пространства имен. Но я не могу ADL-ify их обоих. Возможно, мне не хватает чего-то действительно очевидного.

Btw, в этом случае я мог бы просто полностью специализировать каждый из и sandwich::sub. Вызывающие вызывали бы using один или другой, и получили бы правильную функцию. Однако исходный вопрос говорит о частичной специализации, поэтому можем ли мы сделать вид, что специализация - это не вариант, без меня на самом деле сделать HeapOfHam шаблоном класса?

+0

Не уверен в вопросе 'sub' для сплющенных пространств имен. Я думаю, что у одной была бы такая же проблема, если бы 'sub' была бы функцией-членом. Каким будет операнд этих двух подводных лодок? Если сэндвич: я не могу представить, как мы могли бы арифметически подойти к бутерброду, поэтому «сэндвич», определенный в «арифметике», кажется маловероятным. Если MyInt: Поскольку неявные преобразования фактически не учитываются (если у 'sandwich' есть' (MyInt) '(для количества пластин сыра?) Ctor и мы называем' sub (a, b) ', мы не найдем' sub' в бутерброде, конечно), сначала я не похож на проблему. Я был бы рад, если бы вы это уточнили. –

+0

@litb: Я не совсем слежу за тобой, поэтому я отредактирую, и мы посмотрим, говорим ли мы об одном и том же. –

+0

@lit: сделано. Если вы не знаете ответа, возможно, я задам вопрос. –

1

Если вы пишете библиотеку, которая будет использоваться в другом месте или другими людьми, создайте объект struct/class. Это больше кода, но пользователи вашей библиотеки (возможно, будущее вас!) Будут благодарны вам. ЕСЛИ это один код использования, потеря частичной специализации не повредит вам.