咨询C++中此类模板全特化+包装函数实现模式的通用名称
Common C++ Template Patterns: Reference Guide
Below are essential template patterns, with examples, use cases, and tradeoffs to help you navigate metaprogramming and generic code:
1. SFINAE (Substitution Failure Is Not An Error)
Core Idea: Leverage the rule that failed template parameter substitution doesn't trigger a compile error to selectively enable/disable overloads or specializations.
Example:
#include <type_traits> // Enabled only for integral types template<typename T, typename = std::enable_if_t<std::is_integral_v<T>>> void process(T val) { std::cout << "Processing integral value: " << val << "\n"; } // Enabled only for floating-point types template<typename T, typename = std::enable_if_t<std::is_floating_point_v<T>>> void process(T val) { std::cout << "Processing floating-point value: " << val << "\n"; }
Use Case: Handle complex type-based overloads, avoid ambiguity, and enable conditional template instantiation.
Pros: Flexible for combining multiple type traits, no need for exhaustive specializations.
Cons: Debugging is hard (error messages are cryptic), and complex conditions can hurt readability.
2. Tag Dispatch
Core Idea: Use empty "tag" types to dispatch function calls to different overloads at compile time.
Example:
#include <type_traits> // Tag types struct integral_tag {}; struct floating_point_tag {}; // Helper to generate the correct tag template<typename T> constexpr auto get_type_tag() { if constexpr(std::is_integral_v<T>) return integral_tag{}; else if constexpr(std::is_floating_point_v<T>) return floating_point_tag{}; } // Overloads for each tag void process_impl(int val, integral_tag) { std::cout << "Integral tag route: " << val << "\n"; } void process_impl(double val, floating_point_tag) { std::cout << "Floating-point tag route: " << val << "\n"; } // Wrapper to dispatch template<typename T> void process(T val) { process_impl(val, get_type_tag<T>()); }
Use Case: Simple, readable compile-time branching based on type traits—easier to debug than SFINAE.
Pros: Clear overload logic, straightforward debugging, no cryptic error messages.
Cons: Requires defining extra tag types; becomes unwieldy with multiple condition combinations.
3. Policy-Based Design
Core Idea: Abstract reusable behaviors into "policy" classes, which are injected into a main class/function via template parameters to configure behavior.
Example:
// Sort policy classes struct QuickSortPolicy { template<typename T> static void sort(T* data, size_t size) { std::cout << "Executing fast quicksort\n"; // Actual quicksort implementation here } }; struct MergeSortPolicy { template<typename T> static void sort(T* data, size_t size) { std::cout << "Executing stable mergesort\n"; // Actual mergesort implementation here } }; // Main sorter class using policies template<typename SortPolicy> class Sorter { public: template<typename T> void sort(T* data, size_t size) { SortPolicy::sort(data, size); } }; // Usage Sorter<QuickSortPolicy> fast_sorter; Sorter<MergeSortPolicy> stable_sorter;
Use Case: Build configurable, modular components (e.g., container allocators, logging strategies, sorting algorithms).
Pros: High modularity, compile-time binding (no runtime overhead), easy to swap behaviors.
Cons: Requires upfront design of policy interfaces; template parameter lists can grow long with multiple policies.
4. CRTP (Curiously Recurring Template Pattern)
Core Idea: Pass the derived class as a template parameter to the base class to enable static polymorphism, avoiding virtual function overhead.
Example:
template<typename Derived> class BaseInterface { public: void do_work() { // Call the derived class's implementation static_cast<Derived*>(this)->impl(); } }; class ConcreteImpl : public BaseInterface<ConcreteImpl> { public: void impl() { std::cout << "Concrete implementation doing work\n"; } };
Use Case: Performance-sensitive interfaces (e.g., custom iterators, math libraries) where runtime polymorphism is too costly.
Pros: Zero virtual function overhead, allows base classes to reuse derived class functionality.
Cons: Non-intuitive syntax, doesn't support runtime polymorphism, and requires strict adherence to the CRTP structure.
5. Type Traits
Core Idea: Compile-time utilities to query or modify type properties (e.g., "is this type a pointer?" "can this type be copied?").
Example:
#include <type_traits> // Custom trait: check if a type is a pointer to an integral type template<typename T> struct is_integral_pointer : std::false_type {}; template<typename T> struct is_integral_pointer<T*> : std::is_integral<T> {}; // Usage static_assert(is_integral_pointer<int*>::value, "int* is a pointer to integral"); static_assert(!is_integral_pointer<double*>::value, "double* is not a pointer to integral");
Use Case: Provide compile-time type information for other patterns (SFINAE, tag dispatch), validate template arguments, or enable conditional logic.
Pros: Standard library provides a rich set of pre-defined traits, enables precise compile-time type checks.
Cons: Custom traits require writing multiple specializations, which can be error-prone for complex type relationships.
6. Variadic Templates
Core Idea: Support arbitrary numbers of template parameters, enabling functions/classes that handle variable argument lists at compile time.
Example:
// Recursive sum function for variadic arguments template<typename T> T sum(T val) { return val; } template<typename T, typename... Args> T sum(T first, Args... rest) { return first + sum(rest...); } // Usage int total = sum(1, 2, 3, 4); // total = 10 double avg = sum(1.5, 2.5, 3.5) / 3; // avg = 2.5
Use Case: Implement functions with variable arguments (e.g., formatters, container initializers), or perform metaprogramming tasks involving parameter packs.
Pros: Extreme flexibility, handles any number/type of arguments, no runtime overhead.
Cons: Recursive logic can be hard to debug, error messages are often unhelpful, and complex pack expansions can reduce readability.
内容的提问来源于stack exchange,提问作者Francis Cugler

