Отправка тегов против статических методов на частично специализированных классах
Предположим, что я хочу написать общую функцию void f<T>()
, которая делает одно, если T
- тип POD, а другая вещь, если T
- не-POD (или любой другой произвольный предикат).
Одним из способов достижения этой цели будет использование шаблона отправки тегов, например стандартная библиотека, с категориями итератора:
template <bool> struct podness {};
typedef podness<true> pod_tag;
typedef podness<false> non_pod_tag;
template <typename T> void f2(T, pod_tag) { /* POD */ }
template <typename T> void f2(T, non_pod_tag) { /* non-POD */ }
template <typename T>
void f(T x)
{
// Dispatch to f2 based on tag.
f2(x, podness<std::is_pod<T>::value>());
}
Альтернативой может быть использование статической функции-члена частично специализированных типов:
template <typename T, bool> struct f2;
template <typename T>
struct f2<T, true> { static void f(T) { /* POD */ } };
template <typename T>
struct f2<T, false> { static void f(T) { /* non-POD */ } };
template <typename T>
void f(T x)
{
// Select the correct partially specialised type.
f2<T, std::is_pod<T>::value>::f(x);
}
Каковы плюсы и минусы использования одного метода над другим? Что бы вы порекомендовали?
Ответы
Ответ 1
Я хочу отправить тегу, потому что:
- Легко распространяться с помощью новых тегов
- Простая в использовании наследование (пример)
- Это довольно распространенная техника в общем программировании
Мне кажется сложным добавить третий вариант во втором примере. Когда вы захотите добавить, например, не-POD-of-PODs, вам придется заменить bool
в template <typename T, bool> struct f2;
на что-то другое (int
, если вам нравится =)) и заменить все struct f2<T, bool-value>
с struct f2<T, another-type-value>
. Так что для меня второй вариант выглядит едва растяжимым. Пожалуйста, поправьте меня, если я ошибаюсь.
Ответ 2
A читаемая альтернатива [boost|std]::enable_if
, теги и частичная специализация для простой отправки компиляции, которые мне нравятся, следующие:
[Помните, что логические переменные имеют преобразование в целые числа, что массивы нулевой длины являются недействительными и что шаблоны-нарушения отбрасываются (SFINAE). Кроме того, char (*)[n]
является указателем на массив элементов n
.]
template <typename T>
void foo(T, char (*)[is_pod<T>::value] = 0)
{
// POD
}
template <typename T>
void foo(T, char (*)[!is_pod<T>::value] = 0)
{
// Non POD
}
У него также есть преимущество в том, что не нужны внешние классы, которые загрязняют пространство имен. Теперь, если вы хотите экрнализировать предикат, как в своем вопросе, вы можете сделать:
template <bool what, typename T>
void foo(T, char (*)[what] = 0)
{
// taken when what is true
}
template <bool what, typename T>
void foo(T, char (*)[!what] = 0)
{
// taken when what is false
}
Использование:
foo<std::is_pod<T>::value>(some_variable);
Ответ 3
Фактически оба имеют только шаблон отправки тегов. Бывший называется тегом диспетчеризации экземпляром, а последний - тегом, отправляющим по типу.
Баренд, главный автор Boost.Geometry, объясняет оба метода и предпочитает последний. Это широко используется в Boost.Geometry. Ниже приведены преимущества:
- Нет необходимости создавать экземпляр тега, поскольку его единственная цель - различать
- Легко определить новые типы и константы на основе тегов
- Аргументы могут быть отменены в интерфейсе, то есть
distance(point, polygon);
и distance(polygon, point);
могут иметь только одну реализацию
Ответ 4
Я знаю, что это старый вопрос с уже принятым ответом, но это может быть жизнеспособной альтернативой:
template<typename T>
std::enable_if_t<std::is_pod<T>::value> f(T pod)
{
}
template<typename T>
std::enable_if_t<!std::is_pod<T>::value> f(T non_pod)
{
}