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
Related Articles
- C++ Type Traits Complete Guide
- C++ Tag Dispatch Complete Guide | Compile-Time Function Selection & STL Optimization
- C++ Expression Templates | Advanced Lazy Evaluation Techniques
Related Posts
- C++ Tag Dispatch Complete Guide
- C++ Expression Templates
- C++ SFINAE & Concepts
- C++ Type Traits
- C++ Algorithm Copy
Keywords
C++, tag dispatch, metaprogramming, templates, STL, compile-time optimization, iterator categories, type traits
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- C++ Expression Templates | ‘지연 평가’ 고급 기법
- C++ Tag Dispatch 완벽 가이드 | 컴파일 타임 함수 선택과 STL 최적화
- C++ constexpr if | ‘컴파일 타임 분기’ 가이드
이 글에서 다루는 키워드 (관련 검색어)
C++, Tag Dispatch, Metaprogramming, Templates, STL, Compile-Time 등으로 검색하시면 이 글이 도움이 됩니다.