Диспетчеризация тегов и статические методы в частично специализированных классах

Предположим, я хочу написать общую функцию 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);
}

Каковы плюсы и минусы использования одного метода по сравнению с другим? Что бы вы порекомендовали?


person Peter Alexander    schedule 02.08.2011    source источник
comment
Все, что плывет в вашей лодке. Я нахожу вторую версию более типичной и привлекательной, потому что в ней меньше вспомогательного кода и меньше скрытых понятий. Также я бы добавил переадресацию для аргумента!   -  person Kerrek SB    schedule 02.08.2011


Ответы (4)


Я хотел бы отправлять теги, потому что:

  • Легко расширить с помощью новых тегов
  • Простое в использовании наследование (пример)
  • Это довольно распространенная техника в универсальном программировании.

Мне кажется сложным добавить третий вариант во второй пример. Когда вы захотите добавить, например, тип не-POD-of-POD, вам придется заменить bool в template <typename T, bool> struct f2; чем-то другим (int, если хотите =)) и заменить все struct f2<T, bool-value> на struct f2<T, another-type-value>. Так что для меня второй вариант выглядит малорасширяемым. Пожалуйста, поправьте меня, если я ошибаюсь.

person tim    schedule 03.08.2011
comment
Одним из способов расширения было бы использование параметра шаблона перечисления, но я согласен, что это было бы немного более неуклюже. - person Peter Alexander; 03.08.2011

Удобочитаемая альтернатива [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);
person Alexandre C.    schedule 02.08.2011
comment
Да, это умно, но если вы не знаете о SFINAE, то у этого есть серьезный фактор «WTF», которого нет у других. Хотя я ценю краткость. - person Peter Alexander; 02.08.2011
comment
@Peter: SFINAE уже имеет большой фактор WTF. Использование typename std::enable_if<whatever>::type действительно запутывает часть whatever imho. Здесь он стоит ясно, в скобках. - person Alexandre C.; 02.08.2011
comment
Цитата из Парка Юрского периода, Умница. Чтобы сделать его более читабельным, не следует ли вам использовать '= NULL' или еще лучше '= nullptr'? - person kornman00; 07.11.2013
comment
= 0 не работает с -Werror=zero-as-null-pointer-constant в некоторых реализациях. Для меньшей детализации используйте вместо этого = {}, когда доступен C++ 11 или выше (в противном случае такой проблемы быть не должно). - person FrankHB; 31.10.2015
comment
@АлександрК. Они уже могут быть в равной степени WTF, но enable_if явно более удобен для поисковых систем. - person FrankHB; 31.10.2015
comment
@FrankHB: Хорошо, что поисковые системы изобилуют. Однако в кодовой базе поиск используемого мной трюка можно выполнить с помощью регулярного выражения. Кроме того, С++ 14 предоставляет std::enable_if_t, что позволяет избежать части typename ...::type, о которой я разглагольствую. - person Alexandre C.; 31.10.2015
comment
@АлександрК. Что ж, можно выполнить поиск в кодовой базе, чтобы найти все вхождения, но новичку все еще трудно понять материал WTF. Наберите в гугле enable_if/enable_if_t и он, наверное, поймет, почему через несколько минут должна быть такая каша. Regexp здесь не поможет. - person FrankHB; 31.10.2015
comment
@FrankHB: Я согласен с этим - в конечном итоге это зависит от соглашения о коде вашей команды. Тем не менее, лучше всего, вероятно, держаться подальше от SFINAE, за исключением случаев, когда это действительно необходимо, именно из-за фактора WTF. - person Alexandre C.; 31.10.2015

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

Баренд, основной автор Boost.Geometry, объясняет оба метода и предпочитает последний. Это широко используется в Boost.Geometry. Вот краткое изложение преимуществ:

  • Нет необходимости создавать экземпляр тега, так как его единственная цель состоит в том, чтобы различать
  • Легко определять новые типы и константы на основе тегов.
  • Аргументы могут быть изменены в интерфейсе, т.е., скажем, distance(point, polygon); и distance(polygon, point); могут иметь только одну реализацию.
person legends2k    schedule 07.10.2013

Я знаю, что это старый вопрос с уже принятым ответом, но это может быть жизнеспособной альтернативой:

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)
{
}
person Mircea Ispas    schedule 21.10.2015