Tag dispatch versus static methods on partially specialised classes

C++MetaprogrammingGeneric ProgrammingPartial Specialization

C++ Problem Overview


Suppose I want to write a generic function void f<T>(), which does one thing if T is a POD type and another thing if T is non-POD (or any other arbitrary predicate).

One way to achieve this would be to use a tag-dispatch pattern like the standard library does with iterator categories:

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>());
}

An alternative would be to use static member function of partially specialised types:

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);
}

What are the pros and cons of using one method over the other? Which would you recommend?

C++ Solutions


Solution 1 - C++

I would like tag dispatch because:

  • Easy to extend with new tags
  • Easy to use inheritance (example)
  • It is fairly common technique in generic programming

It seems tricky to me to add third variant in second example. When you'll want to add, for example non-POD-of-PODs type you'll have to replace bool in template <typename T, bool> struct f2; with something other (int if you like =) ) and replace all struct f2<T, bool-value> with struct f2<T, another-type-value>. So that for me the second variant looks hardly extensible. Please correct me if I wrong.

Solution 2 - C++

A readable alternative to [boost|std]::enable_if, tags and partial specialization for simple compile-time dispatch that I like is the following:

[Remember that booleans have conversion to integers, that zero-length arrays are invalid and that offending templates are discarded (SFINAE). Also, char (*)[n] is a pointer to an array of n elements.]

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
}

It also has the advantage of not needing external classes which pollute the namespace. Now, if you want to externalize the predicate like in your question, you can do:

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
}

Usage:

foo<std::is_pod<T>::value>(some_variable);

Solution 3 - C++

Actually both are of the tag dispatching pattern only. Former is called tag dispatching by instance and the latter is tag dispatching by type.

Barend, the primary author of Boost.Geometry, explains both the methods and prefers the latter. This is used in Boost.Geometry extensively. Here're the advantages summarized:

  • It is unnecessary to instantiate the tag since its only purpose is to differentiate
  • It is easy to define new types and constants based on tags
  • Arguments can be reversed in the interface i.e. say distance(point, polygon); and distance(polygon, point); can both have only one implementation

Solution 4 - C++

I know this is an old question with the answer already accepted, but this might be a viable alternative:

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)
{
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionPeter AlexanderView Question on Stackoverflow
Solution 1 - C++timView Answer on Stackoverflow
Solution 2 - C++Alexandre C.View Answer on Stackoverflow
Solution 3 - C++legends2kView Answer on Stackoverflow
Solution 4 - C++Mircea IspasView Answer on Stackoverflow