본문으로 건너뛰기
Previous
Next
C++ Tag Dispatching Complete Guide

C++ Tag Dispatching Complete Guide

C++ Tag Dispatching Complete Guide

이 글의 핵심

Master C++ tag dispatching pattern: use empty tag types for compile-time overload resolution. Complete guide with iterator tags, serialization strategies, memcpy optimization, and if constexpr alternatives.

What is Tag Dispatching?

Tag dispatching uses empty tag types to drive function overload resolution at compile time, enabling type-specific optimizations without runtime branching.

// Tag types
struct IntTag {};
struct FloatTag {};
// Tag dispatch implementations
void processImpl(int value, IntTag) {
    cout << "Integer: " << value << endl;
}
void processImpl(double value, FloatTag) {
    cout << "Float: " << value << endl;
}
// Unified interface
template<typename T>
void process(T value) {
    if constexpr (is_integral_v<T>) {
        processImpl(value, IntTag{});
    } else {
        processImpl(value, FloatTag{});
    }
}
int main() {
    process(10);    // Integer
    process(3.14);  // Float
}

Iterator Tag Dispatching

STL uses iterator category tags to select optimal algorithms. Random-access iterators can jump directly (it += n), while input iterators must increment one-by-one.

#include <iterator>
// Implementation functions
template<typename Iter>
void advanceImpl(Iter& it, int n, input_iterator_tag) {
    // Input iterator: one step at a time
    while (n--) ++it;
}
template<typename Iter>
void advanceImpl(Iter& it, int n, random_access_iterator_tag) {
    // Random access iterator: jump directly
    it += n;
}
// Unified interface
template<typename Iter>
void advance(Iter& it, int n) {
    advanceImpl(it, n, typename iterator_traits<Iter>::iterator_category{});
}
int main() {
    vector<int> v = {1, 2, 3, 4, 5};
    auto it = v.begin();
    advance(it, 3);  // Random access (fast)
    
    list<int> l = {1, 2, 3, 4, 5};
    auto it2 = l.begin();
    advance(it2, 3);  // Input iterator (slow)
}

Practical Examples

Example 1: Distance Calculation

// Input iterator
template<typename Iter>
typename iterator_traits<Iter>::difference_type
distanceImpl(Iter first, Iter last, input_iterator_tag) {
    typename iterator_traits<Iter>::difference_type n = 0;
    while (first != last) {
        ++first;
        ++n;
    }
    return n;
}
// Random access iterator
template<typename Iter>
typename iterator_traits<Iter>::difference_type
distanceImpl(Iter first, Iter last, random_access_iterator_tag) {
    return last - first;  // O(1)
}
template<typename Iter>
auto distance(Iter first, Iter last) {
    return distanceImpl(first, last, 
        typename iterator_traits<Iter>::iterator_category{});
}
int main() {
    vector<int> v = {1, 2, 3, 4, 5};
    cout << distance(v.begin(), v.end()) << endl;  // 5 (fast)
    
    list<int> l = {1, 2, 3, 4, 5};
    cout << distance(l.begin(), l.end()) << endl;  // 5 (slow)
}

Example 2: Type-Based Serialization

struct PrimitiveTag {};
struct ContainerTag {};
struct CustomTag {};
// Type traits
template<typename T>
struct SerializeTraits {
    using tag = CustomTag;
};
template<> struct SerializeTraits<int> { using tag = PrimitiveTag; };
template<> struct SerializeTraits<double> { using tag = PrimitiveTag; };
template<typename T> struct SerializeTraits<vector<T>> { using tag = ContainerTag; };
// Implementations
template<typename T>
string serializeImpl(const T& value, PrimitiveTag) {
    return to_string(value);
}
template<typename T>
string serializeImpl(const T& container, ContainerTag) {
    string result = "[";
    for (const auto& item : container) {
        result += serialize(item) + ",";
    }
    result += "]";
    return result;
}
template<typename T>
string serializeImpl(const T& value, CustomTag) {
    return value.toString();  // Call custom method
}
// Unified interface
template<typename T>
string serialize(const T& value) {
    return serializeImpl(value, typename SerializeTraits<T>::tag{});
}
int main() {
    cout << serialize(42) << endl;
    cout << serialize(vector<int>{1, 2, 3}) << endl;
}

Example 3: Copy Optimization

struct TrivialTag {};
struct NonTrivialTag {};
template<typename T>
using CopyTag = conditional_t<
    is_trivially_copyable_v<T>,
    TrivialTag,
    NonTrivialTag
>;
// Trivial types (memcpy)
template<typename T>
void copyImpl(T* dest, const T* src, size_t n, TrivialTag) {
    memcpy(dest, src, n * sizeof(T));
}
// Non-trivial types (constructor)
template<typename T>
void copyImpl(T* dest, const T* src, size_t n, NonTrivialTag) {
    for (size_t i = 0; i < n; i++) {
        new (&dest[i]) T(src[i]);
    }
}
template<typename T>
void copy(T* dest, const T* src, size_t n) {
    copyImpl(dest, src, n, CopyTag<T>{});
}
int main() {
    int arr1[5] = {1, 2, 3, 4, 5};
    int arr2[5];
    copy(arr1, arr2, 5);  // memcpy (fast)
    
    string str1[3] = {"a", "b", "c"};
    string str2[3];
    copy(str1, str2, 3);  // Constructor (safe)
}

Example 4: Algorithm Optimization

struct SmallSizeTag {};
struct LargeSizeTag {};
template<size_t N>
using SizeTag = conditional_t<(N < 10), SmallSizeTag, LargeSizeTag>;
// Small arrays: bubble sort
template<typename T, size_t N>
void sortImpl(T (&arr)[N], SmallSizeTag) {
    for (size_t i = 0; i < N; i++) {
        for (size_t j = i + 1; j < N; j++) {
            if (arr[j] < arr[i]) {
                swap(arr[i], arr[j]);
            }
        }
    }
}
// Large arrays: quick sort
template<typename T, size_t N>
void sortImpl(T (&arr)[N], LargeSizeTag) {
    sort(begin(arr), end(arr));
}
template<typename T, size_t N>
void sort(T (&arr)[N]) {
    sortImpl(arr, SizeTag<N>{});
}
int main() {
    int small[5] = {5, 2, 8, 1, 9};
    sort(small);  // Bubble sort
    
    int large[100];
    sort(large);  // Quick sort
}

if constexpr vs Tag Dispatching

// if constexpr (C++17)
template<typename T>
void process(T value) {
    if constexpr (is_integral_v<T>) {
        // Integer processing
    } else {
        // Float processing
    }
}
// Tag Dispatching (C++11)
template<typename T>
void process(T value) {
    processImpl(value, TypeTag<T>{});
}

Selection criteria:

  • C++17+: if constexpr (more concise)
  • C++11/14: Tag dispatching
  • Complex branching: Tag dispatching (better readability)

Common Issues

Issue 1: Missing tag type

// ❌ No tags
template<typename Iter>
void advance(Iter& it, int n) {
    it += n;  // Doesn't work for all iterators
}
// ✅ Tag dispatch
template<typename Iter>
void advance(Iter& it, int n) {
    advanceImpl(it, n, typename iterator_traits<Iter>::iterator_category{});
}

Issue 2: Wrong tag selection

// ❌ Wrong condition
template<typename T>
using Tag = conditional_t<sizeof(T) == 4, SmallTag, LargeTag>;
// ✅ Meaningful condition
template<typename T>
using Tag = conditional_t<is_trivially_copyable_v<T>, TrivialTag, NonTrivialTag>;

FAQ

Q1: When should I use tag dispatching?

A:

  • Type-based optimization
  • STL algorithm implementation
  • Compile-time branching

Q2: if constexpr vs tag dispatching?

A: In C++17+, if constexpr is more concise. However, for complex cases, tag dispatching can be clearer.

Q3: Performance difference?

A: Both are resolved at compile time, so there’s no runtime difference.

Q4: How do I define tag types?

A: Define as empty structs. No data members needed.

Q5: Does STL use this?

A: Yes, iterator_category is a prime example.

Q6: Learning resources for tag dispatching?

A:

  • “Effective STL” (Scott Meyers)
  • cppreference.com
  • STL source code

Keywords

C++, tag dispatch, metaprogramming, templates, STL, compile-time optimization, iterator categories, type traits


같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.


이 글에서 다루는 키워드 (관련 검색어)

C++, Tag Dispatch, Metaprogramming, Templates, STL, Compile-Time 등으로 검색하시면 이 글이 도움이 됩니다.