Tag Dispatch in C++: Selecting Functions at Compile Time
Tag dispatch is a C++ template metaprogramming idiom used to select
different function implementations at compile time based on type
properties. By passing an instantiated, empty struct—called a "tag"—as
an extra argument to an overloaded implementation function, the compiler
uses standard overload resolution to select the most suitable version.
This mechanism delivers zero runtime overhead and clear separation
between algorithm variants, serving as the foundational design pattern
behind standard utilities such as std::advance.
How Tag Dispatch Works
Tag dispatch relies on three main components: tag types, overloaded implementation functions, and a public wrapper function that queries a type trait.
- Tag Types: Lightweight, empty structs that represent specific capabilities, categories, or traits of a type.
- Implementation Functions: Overloaded functions
(
implfunctions) that take an instance of a specific tag type as an auxiliary parameter. - Public Interface: A single entry-point function that extracts the appropriate tag from a type trait and passes a default-constructed instance of that tag to the implementation function.
Because tag structs contain no state, optimizing compilers eliminate the tag argument entirely, leaving behind direct, optimal machine instructions without virtual dispatch or runtime branching.
Implementation Example:
Custom advance
A classic demonstration of tag dispatch is advancing an iterator.
While random-access iterators can jump directly to a target position in
constant time (O(1)), forward iterators must step through
elements sequentially in linear time (O(N)).
#include <iostream>
#include <vector>
#include <list>
#include <iterator>
namespace detail {
// Implementation for random access iterators: O(1)
template <typename Iter, typename Distance>
void advance_impl(Iter& it, Distance n, std::random_access_iterator_tag) {
it += n;
}
// Implementation for bidirectional/input iterators: O(N)
template <typename Iter, typename Distance>
void advance_impl(Iter& it, Distance n, std::input_iterator_tag) {
while (n > 0) {
++it;
--n;
}
}
}
// Public interface
template <typename Iter, typename Distance>
void my_advance(Iter& it, Distance n) {
// 1. Extract the iterator category tag using traits
using category = typename std::iterator_traits<Iter>::iterator_category;
// 2. Dispatch to the matching overload by constructing the tag
detail::advance_impl(it, n, category{});
}Leveraging Tag Hierarchies for Fallback Behavior
Tag dispatch becomes particularly powerful when tags are organized in an inheritance hierarchy. The C++ standard library defines iterator categories using inheritance:
struct input_iterator_tag {};
struct forward_iterator_tag : public input_iterator_tag {};
struct bidirectional_iterator_tag : public forward_iterator_tag {};
struct random_access_iterator_tag : public bidirectional_iterator_tag {};When an exact match for a tag does not exist, standard C++ overload
resolution falls back to a base class. In the example above, if a
std::bidirectional_iterator_tag is passed, the compiler
matches advance_impl(..., std::input_iterator_tag) because
bidirectional_iterator_tag derives from
input_iterator_tag. This enables library authors to provide
specialized implementations for advanced types while automatically
falling back to generic implementations for others.
Tag Dispatch vs. Modern Alternatives
While tag dispatch remains a core pattern in legacy codebases and specific template designs, modern C++ offers direct alternatives:
- C++17
if constexpr: Allows compile-time branching within a single function body without needing separate overloaded helper functions or tag types. - C++20 Concepts: Enables constraint-based overload
resolution directly on function signatures using
requiresclauses, producing more concise code and clearer compiler diagnostics.
Despite these advancements, tag dispatch remains an essential technique. It demonstrates how standard overload resolution can resolve complex compile-time polymorphism without introducing language extensions beyond fundamental template types.