51

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?

Aziz Shaikh
  • 16,245
  • 11
  • 62
  • 79
Peter Alexander
  • 53,344
  • 14
  • 119
  • 168
  • 3
    Whatever floats your boat. I find the second version more "typetraity" and appealing, because there's less auxiliary code and fewer hidden concepts. Also I'd add forwarding for the argument! – Kerrek SB Aug 02 '11 at 18:57

4 Answers4

17

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.

tim
  • 788
  • 4
  • 14
16

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);
Alexandre C.
  • 55,948
  • 11
  • 128
  • 197
  • 2
    Yes, it's clever, but if you don't know about SFINAE then this has a serious 'WTF' factor to it that the others don't have. I appreciate succinctness though. – Peter Alexander Aug 02 '11 at 19:28
  • 2
    @Peter: SFINAE already has a big WTF factor. Using `typename std::enable_if::type` really obfuscates the `whatever` part imho. Here it stands clear, inside its brackets. – Alexandre C. Aug 02 '11 at 19:55
  • 1
    To quote Jurassic Park, "Clever girl". To make it more readable, shouldn't you opt to use '= NULL' or better yet '= nullptr'? – kornman00 Nov 06 '13 at 21:34
  • `= 0` does not work with `-Werror=zero-as-null-pointer-constant` in some implementations. For less verbosity, use `= {}` instead when C++11 or above is available (otherwise there should be no such problem). – FrankHB Oct 31 '15 at 05:18
  • 4
    @AlexandreC. They may already be equally "WTF" but `enable_if` is obviously more search-engine-friendly. – FrankHB Oct 31 '15 at 05:23
  • @FrankHB: Good point abound the search engines. However, in a codebase, searching for the trick I use can be done with a regexp. Also, C++14 provides `std::enable_if_t` which avoids the `typename ...::type` part that I rant about. – Alexandre C. Oct 31 '15 at 08:35
  • @AlexandreC. Well, it is doable to search the codebase to find all the occurrence, but it is still hard for a newbie to _comprehend_ the "WTF" stuff. Type "enable_if"/"enable_if_t" in Google and he would probably know why there should be such a mess after several minutes. Regexp does not help here. – FrankHB Oct 31 '15 at 09:06
  • 1
    @FrankHB: I agree with this -- ultimately this depends on the code convention of your team. However, the best is probably to stay away from SFINAE except when really required, precisely because of the WTF factor. – Alexandre C. Oct 31 '15 at 09:17
12

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
legends2k
  • 31,634
  • 25
  • 118
  • 222
2

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)
{
}
Mircea Ispas
  • 20,260
  • 32
  • 123
  • 211