cpp_function_templates_how_do_they_really_work
C++ Function Templates: How Do They Really Work? - Walter E. Brown
Some misinformation (myth-information2) found online
Misconception 1: "Function templates are special functions."
- Clarification: Function templates are not functions; they are blueprints or recipes for creating functions when instantiated with specific types. The term "special functions" in C++ refers to member functions that the compiler can automatically generate, such as constructors, destructors, and assignment operators. Additionally, in the context of mathematics and C++17's inclusion of certain mathematical functions in
<cmath>, "special functions" refers to well-defined mathematical functions like Bessel and Legendre functions.
Misconception 2: "A function template behaves like a function."
- Clarification: Function templates themselves have no behavior and cannot be directly called or have their addresses taken. They are templates from which actual functions can be instantiated. A function template's instantiation results in a function that can be called, but the template itself is not a function and does not exist at runtime.
Misconception 3: "Here is the templated function."
- Clarification: The correct terminology is "function template," not "templated function" or "template function." The distinction is important because it emphasizes that what is being defined is a template for creating functions, not a function itself. This mirrors the distinction between "chocolate milk" (a milk-based beverage flavored with chocolate) and "milk chocolate" (a type of chocolate that includes milk powder or condensed milk).
So, what is a function template?
Regular Function vs. Function Template
auto min( int a, int b ) { return b <a ? b : a; } // ints only`
template< class T > // any copyable type, T, with an operator<
auto min( T a, T b) { return b <a ? b : a; }
-
Regular Function (
minforint): This function is explicitly typed forintarguments. It's straightforward and works perfectly for integers but lacks flexibility. If you need the same logic for other types, you'd have to write new functions for each type, leading to code duplication. -
Function Template: The template version of
minuses a type placeholderTinstead of a specific type likeint. This allows theminfunction to be used with any type that supports the<operator, significantly increasing its utility without sacrificing type safety or requiring multiple versions of the same logic.
Key Concepts
-
Template as a Blueprint: The function template acts as a blueprint from which the compiler can generate specific function instances as needed. This analogy helps understand the template's role in generating code tailored to the specific types used in function calls.
-
On-demand Instantiation: The compiler generates specific instances of a template function only when required, typically at the point of a function call with specific types. This efficient mechanism ensures that only the necessary code is generated, keeping the compiled binary size optimized.
-
Header File Placement: Function templates are usually defined in header files to ensure their complete definitions are visible to the compiler wherever they're used. This visibility is crucial because the compiler must see the entire template definition to instantiate it correctly for the given types.
Practical Implications
-
Generic Programming: Function templates are fundamental to generic programming in C++, allowing developers to write flexible, reusable code that works with any type that meets the template's requirements.
-
Type Safety and Code Reuse: By using templates, you can write a single piece of logic that applies to many types, avoiding code duplication while maintaining type safety.
-
Compile-time Type Checking: Despite their flexibility, templates do not sacrifice type safety. The compiler will still perform type checking for template instantiations, catching type-related errors at compile time.
Instructive example
First, let's overload the name g (braced def'n bodies omitted):
template <class T> void g(T const&) // function template
template <> void g(int const&) // explicit specialization
void g(double) // ordinary function
- Declaration 1: A function template that accepts any type
Tas long asTis aconstreference. - Declaration 2: An explicit specialization of the function template for
int const&. This specialization provides a custom implementation forinttypes but does not introduce a new function name into the namespace. Instead, it specifies behavior for the template whenTisint. - Declaration 3: An ordinary (non-template) function that specifically handles
doubletypes.
Understanding Declarations and Names
-
Number of Declarations: There are three declarations provided. Each serves a different purpose: a generic template for any type
T, a specialization forint, and a regular function fordouble. -
Number of Names Introduced: Despite the three declarations, only two unique names (or mangled names, considering C++'s name mangling for function overloading) are introduced. This is because:
- The function template and its explicit specialization share the same name in the context of the compiler's name resolution. The explicit specialization is not a separate entity but a specific case of the template.
- The ordinary function introduces the second unique name because it is not a template and hence is considered separately by the compiler.
Key Takeaways
- Function Templates Are Not Functions: They are blueprints for generating functions based on the types with which they are instantiated.
- Specializations Do Not Introduce New Names: They customize the behavior of templates for specific types but under the same name as the primary template.
- Importance of Complete Visibility: To use a template (and by extension, its specializations), its complete declaration must be visible to the compiler. This is why templates are usually defined in header files.
Questions ... provided below declarations
// From those same 3 declarations...
template <class T> void g(T const&); // function template ... X
template <> void g(int const&); // explicit specialization ...X
void g(double) // ordinary function ... V
// ... at most 2 candidate declarations will be considered!
// What are those 2 candidate declarations?
// The ordinary function declaration void g<>(D const&); , and ...
// 2 A synthesized function declaration ; :
// ype D is usually deduced (from the argument's type in the call).
// Then the function template's declaration is copied, with each
// instance of template param T replaced by the deduced type D.
// Ensure this substitution by writing void g<D>(D const&); .
- A function template:
template <class T> void g(T const&); - An explicit specialization for
int:template <> void g<int>(int const&); - An ordinary function for
double:void g(double);
Overload Resolution Process
For a given call to g, at most two candidate declarations will be considered:
- The ordinary function (
void g(double);), which is always a candidate when the argument matches or can be implicitly converted to its parameter type. - A synthesized function declaration, which is a result of deducing the type
Dfrom the call's argument and substitutingTin the function template declaration withD. This effectively generates a declaration likevoid g<D>(D const&);for the purpose of overload resolution.
Scenarios
-
Call with an
int:- The explicit specialization for
int(template <> void g<int>(int const&);) becomes the synthesized candidate. - The ordinary function is not an exact match and requires a conversion, so it's less preferred.
- Result: The explicit specialization for
intis called.
- The explicit specialization for
-
Call with a
double:- The ordinary function is an exact match and becomes a candidate.
- No template deduction is needed since the exact match is found.
- Result: The ordinary function for
doubleis called.
-
Call with a
char:- The function template is instantiated with
char(void g<char>(char const&);) as the synthesized candidate. - The ordinary function is less preferred due to type conversion required.
- Result: The instantiated template for
charis called.
- The function template is instantiated with
-
Call with an array of
char:- The function template is instantiated with
char[]or more preciselychar const(&)[N]whereNis the size of the array, as the synthesized candidate. - The ordinary function is less preferred due to type conversion required.
- Result: The instantiated template for
char[]is called.
- The function template is instantiated with
-
Call with a custom type (e.g.,
std::string):- The function template is instantiated with the custom type (
void g<std::string>(std::string const&);) as the synthesized candidate. - The ordinary function is not a match without an explicit conversion.
- Result: The instantiated template for the custom type is called.
- The function template is instantiated with the custom type (
Why first 2 declarations doesn't involve overload resolutions though?
Templates and Overload Resolution
- Templates Are Not Callable: Function templates are not functions but blueprints for generating functions. They cannot be called directly, thus are not considered in overload resolution.
- Function Template Instantiation: When a function template is called, the compiler generates a concrete function based on the provided template arguments. This instantiation process creates the functions that can participate in overload resolution.
Explicit Specializations and Overload Resolution
- Specializations Are Not Directly Considered: An explicit specialization of a template is indeed a function, but its presence does not directly enter overload resolution as a distinct candidate. It modifies the behavior of the template instantiation process instead.
- Specializations Do Not Introduce New Names: According to the C++ standard, explicit specializations do not introduce new names into the scope. They provide specific implementations for previously declared templates but do not act as independent entities for overload resolution.
Key Takeaways
- Templates as Blueprints: The analogy of a cookie cutter and dough illustrates that templates (cookie cutters) need specific inputs (dough, i.e., template arguments) to produce a result (a cookie, i.e., a function). This helps understand templates as instructions for generating functions rather than callable entities themselves.
- Compiler's Role in Instantiation: The compiler automatically synthesizes function declarations from templates based on the types used in a call. This synthesized function, tailored to the call's arguments, becomes a candidate in overload resolution.
- Overload Resolution Process: During overload resolution, the compiler evaluates viable candidates based on parameters, applicability, and specificity. Template instantiations are considered based on the arguments of a call, while explicit specializations influence the template instantiation process rather than directly participating as overload candidates.
We still need a function to compile/call - where is it
Overload Resolution and Template Instantiation
-
Overload Resolution: This is the first step where the compiler selects the most appropriate candidate for a function call without considering the function definitions. It relies solely on the declarations available to it.
-
Determination of Need for Compilation:
- Case 1: Preexisting Specialization: If a suitable specialization already exists (either through an explicit specialization or a prior implicit instantiation for the same arguments), the compiler may use this specialization. This case avoids re-compilation if the specialization already exists.
- Case 2: No Preexisting Specialization: When no suitable specialization exists, the compiler must create one. This involves taking the primary template, substituting the template parameters with the actual arguments used in the call, and then compiling this instantiated template.
The Compilation Process
- From Declaration to Definition: After selecting a declaration through overload resolution, the compiler needs a definition to compile. This step is crucial because, until this point, overload resolution has only considered declarations, not definitions.
- Substitution and Compilation: The compiler performs substitution, replacing template parameters with the actual call arguments in the template's definition, thereby generating a function specialization tailored to those arguments. This generated specialization is then compiled, producing an executable code that can be called at runtime.
Key Points to Remember
- Overload Resolution is Separate from Compilation: The process of overload resolution and the subsequent compilation of the chosen template specialization are distinct steps. The former decides which template instantiation to use, while the latter actually generates and compiles the code.
- Role of Explicit Specializations: Explicit specializations can preclude the need for the compiler to instantiate a new specialization if they match the call arguments. This can be beneficial for optimizing frequently used specializations or for providing custom implementations.
- Efficiency and Duplication Avoidance: By checking for preexisting specializations, C++ avoids unnecessary compilation work and ensures that the most suitable and efficient code is generated for function calls involving templates.
The role of partial ordering
Overloading Function Templates
- Function templates can be overloaded, just like ordinary functions. This means you can have multiple function templates with the same name but different parameter lists or template parameter lists.
- When a function call is made, and multiple templates are viable candidates, the compiler must decide which template to instantiate.
Partial Ordering of Function Templates
- Objective: To select the most specialized template among all viable candidates for a given call.
- "More Specialized" Criterion: A template is considered "more specialized" than another if it is more constrained or specific in terms of the types it accepts, implying it matches a narrower set of types.
- Algorithm Overview: Without delving into the intricate details, the partial ordering process involves comparing each pair of templates to determine if one can be considered more specialized than the other. This comparison is based on the templates' parameter types and whether one template's parameters can always be matched by the other's but not vice versa.
Example Scenario
- Given Templates:
template <typename T> void f(T);template <typename T> void f(T*);
- Function Call:
f(some_pointer);wheresome_pointeris of some pointer type (e.g.,int*). - Resolution: The second template
f(T*)is considered more specialized for this call because it explicitly requires a pointer type, matching the nature ofsome_pointer. The first templatef(T)is more general and could match any type, including pointers, making it less specialized in this context.
Implications of Partial Ordering
- Precise Template Selection: This process ensures that the most appropriate, specific template is chosen for each call, leading to more efficient and predictable code generation.
- Template Design Consideration: When designing overloaded function templates, understanding partial ordering can help avoid ambiguities and unintended matches, ensuring that each template is appropriately specialized for its intended use cases.
Another example
// let's overload the name h (no tricks; this is valid code):
template <class T> void h(T) {} // (a)
template <> void h(int *) {} // (b)
template <class T> void h(T *) {} // (c)
// Now, let's call h - but which h? (This matters; bodies may differ.)
int *p = nullptr;
h(p); // will call an implicit specialization of (c)
// What templates/candidates will be considered?
void h<int*>(int*); // X: Synthesized from (a)
void h<int>(int*); // O: Synthesized from (c), more specialized
// Note that (b) is an explicit specialization of (a), not of (c).
// Why? Where (b) is declared, only (a) is in scope; haven't yet seen (c).
Given Function Templates and Specialization
- Template (a): A generic template that can accept any type.
- Specialization (b): An explicit specialization of template (a) for
int*. - Template (c): Another generic template specifically for pointer types.
Function Call Analysis
When calling h(p) with p being an int*:
- Candidates for Overload Resolution:
- A synthesized candidate from template (a):
void h<int *>(int *), considering any typeT. - A synthesized candidate from template (c):
void h<int>(int *), considering pointer typesT*, which is more specialized towards pointer types.
- A synthesized candidate from template (a):
- Explicit Specialization (b) Consideration:
- Although (b) is an explicit specialization for
int*, it does not directly participate in the overload resolution for the callh(p). This is because (b) is seen as a specialization of (a) and not (c), due to the order of declaration and scope visibility at the point of (b)'s declaration.
- Although (b) is an explicit specialization for
Determining the More Specialized Candidate
- The compiler performs partial ordering to determine which template is more specialized. In this case, template (c) is considered more specialized because it is explicitly geared towards pointer types (
T*), making it a better match for the callh(p)withpbeing anint*.
Outcome
- The call
h(p)will result in the invocation of a specialization of template (c) forint*, which isvoid h<int>(int *). This specialization is implicitly instantiated based on the function call.
Key Takeaways
- Explicit Specializations Are Tied to Their Primary Templates: An explicit specialization is associated with the primary template visible at the point of its declaration. This association does not change even if more suitable templates are declared later.
- Partial Ordering and Specialization: The compiler uses partial ordering to select the most specialized template for a given call, aiming to match the call's arguments as closely as possible.
Previous example with different ordering
// Here are the same declarations, but in a different order:
template <class T> void h(T) {} // (a)
template <class T> void h(T *) {} // (c)
template <> void h(int *) {} // (b)
// Let's call h the same way - same h as before?
int *p = nullptr;
h(p); // // will call (b), now an explicit specialization of (c)
// What changed by moving (b) to follow both (a) and (c)?
// Both (a) and (c) are now in scope when (b) is seen, so ...
// Partial ordering of function templates picks (c) as the primary
// template corresponding to the explicit specialization (b).
// Why pick (c) over (a)? Just as before, (c)'s parameter is already a
// pointer type, hence (c) is more specialized.
Impact of Declaration Order
-
By placing (b) after both (a) and (c), both generic templates are in scope and known to the compiler before it encounters the explicit specialization (b). This ordering affects how the compiler determines which template (b) specializes:
-
Before Rearrangement: With (b) declared before (c), there's ambiguity about its relationship because only (a) is in scope, suggesting (b) could only be specializing (a).
-
After Rearrangement: With (b) declared after both (a) and (c), the compiler considers both templates when determining the specialization relationship for (b). Partial ordering determines that (c), being more specialized due to its parameter being a pointer type, is the primary template for the explicit specialization (b).
Calling h(p) with int* p
- Overload Resolution: The compiler considers both (a) and (c) as potential candidates. Through partial ordering, it identifies (c) as the more specialized template suitable for a pointer type argument.
- Existence of Specialization (b): Since (b) is now recognized as a specialization of (c) due to the revised ordering, and because (c) is chosen as the template to instantiate based on the argument type, the explicit specialization (b) is called.
Key Observations
- Template Specialization Hierarchy: The order of template declarations can affect the hierarchy of specializations, especially in determining the most specialized template for a given set of arguments.
- Compiler's Template Selection Process: The compiler uses the partial ordering algorithm to select the most specialized template that fits the function call arguments. When an explicit specialization is recognized in relation to a more specialized template, it takes precedence over synthesized specializations.
Change ordering back and add another same specialization
// Finally, let's restore the original order, then append (d):
template <class T> void h(T) {} // (a)
template <> void h(int *) {} // (b)
template <class T> void h(T *) {} // (c)
template <> void h(int *) {} // (d)
// Note the similarity of (b) and (d):
// Is the above still legal code? If so, which h is called now?
int * p = nullptr;
h(p); // this time will call (d), an explicit specialization of (c)
// Note the substitutions in the explicit specializations:
void h<int*>(int*) {} // (b)
void h<int>(int*) {} // (d)
Revised Declarations and Specializations
- Template (a): A generic template for any type
T. - Explicit Specialization (b): Targets
int*, but as an explicit specialization of template (a). - Template (c): A generic template specifically for pointer types
T*. - Explicit Specialization (d): Also targets
int*, intended as an explicit specialization of template (c).
Legal Code and Function Call Outcome
- Legality: The code is legal. C++ allows multiple explicit specializations for the same function template, provided they specialize different templates or the same template in a way that fits different contexts.
- Function Call
h(p): This time, the call will invoke (d), an explicit specialization of template (c), not (a).
Explanation of Specialization Relationships
- Specialization (b): Recognized as a specialization of template (a) because it's declared immediately after (a) and before (c) is introduced. It specializes the template for handling
int*by directly specifyingTasint*. - Specialization (d): Given its position after both (a) and (c), and the partial ordering mechanism, it's correctly associated with template (c). It's more specialized for handling
int*because template (c) is designed for pointer types, making (d) a specialization forint*in the context of pointer-specific handling.
Substitution Details
- In (b): The substitution replaces
Twithint*, directly matching the function parameter's type. - In (d): The substitution is slightly different. Here,
Tis replaced withint, notint*, because template (c) already accounts for the pointer aspect in its definition (T*). This subtle distinction is crucial for understanding how template deduction and specialization work in C++.
Be aware
Key Points on Function Template Specialization
- Unexpected Behavior: Explicit specialization of function templates can interact negatively with overload resolution, leading to confusion and bugs. Overload resolution processes template instantiations and function overloads without considering explicit specializations directly.
- Common Misunderstanding: There is a widespread misconception that explicit specializations influence overload resolution. In reality, specializations are considered only after a template has been selected through overload resolution.
- Long-Standing Advice: The recommendation to avoid explicit specialization of function templates has been known for decades among experienced C++ programmers.
Recommended Practices for Customization
- Prefer Overloading to Specialization: Instead of specializing function templates, write regular function overloads. This approach leverages overload resolution naturally and avoids the pitfalls associated with template specialization.
- Use Indirect Specialization via Class Templates: If specialization is necessary, it can be achieved indirectly through class templates, which do not suffer from the same issues as function template specializations. This method involves:
- Creating a helper class template with a call operator (
operator()) that encapsulates the specialized behavior. - Specializing the class template as needed.
- Forwarding calls from the function template to the class template's call operator, allowing specialization to occur within the class template's scope.
- Creating a helper class template with a call operator (
// (Potential example not from talk)
// Helper class template with operator() for indirect specialization
template<typename T>
struct Processor {
void operator()(const T& value) const {
std::cout << "Processing generic value: " << value << std::endl;
}
};
// Specialization for string type
template<>
struct Processor<std::string> {
void operator()(const std::string& str) const {
std::cout << "Processing string: " << str << std::endl;
}
};
// Forwarding function template to the class template's operator()
template<typename T>
void process(const T& value) {
Processor<T>()(value);
}
Additional Benefits
- Partial Specialization: By using class templates for indirect specialization, programmers can also utilize partial specialization, a powerful template feature not available for function templates. Partial specialization allows for more nuanced and flexible template behavior based on partial matches of template arguments.
- Cleaner Code and Separation of Concerns: This approach promotes a cleaner code structure and separates the concerns between the generic interface provided by the function template and the specialized implementations encapsulated within class templates.
Future changes of the std:: namespace
Explicit Specializations in the Standard Library
- Current Situation: Programmers can explicitly specialize many of the function templates provided by the C++ Standard Library, including those in the algorithms and utilities headers.
- Challenges: Allowing explicit specializations of standard library templates poses significant difficulties for library authors and can lead to unpredictable behavior in code that relies on these templates.
- Proposal: A proposal aims to withdraw permission for explicit specialization of certain function templates within the standard library to ensure consistency and predictability.
Customization Point Objects (CPOs)
- Introduction of CPOs: In response to the limitations of explicit specializations, the proposal introduces customization point objects. CPOs provide a structured way to customize behavior without altering the standard library's templates.
- How CPOs Work: CPOs leverage function objects to allow for customization. Programmers can define their own functions for specific operations (e.g., swapping elements of a custom type) in their namespace, avoiding direct modifications to
std. - Benefits: This approach preserves the ability to customize behavior while maintaining the integrity of standard library templates. It offers a safer, more controlled method of extending or adapting library functionality to specific needs.
Practical Implications
- For Library Users: These changes encourage safer and more maintainable customization practices, reducing the risk of unexpected behaviors caused by specialized standard library templates.
- For Library Authors: Restricting explicit specializations in the standard library simplifies the development and maintenance of library code, ensuring that core functionalities remain consistent across different contexts.
Future Developments
- Standardization Process: These proposals are under consideration for inclusion in future versions of the C++ standard. The outcome of the standardization committee's discussions and votes will determine their adoption.
- Impact on C++ Programming: Adoption of these proposals could significantly affect how C++ developers customize and extend standard library functionalities, emphasizing namespace-scoped custom functions over template specializations.
// "two way dance"
using std::swap;
swap(x, y);
// becomes just
std::swap(x, y );
The "Two-Way Dance"
- Current Practice: To ensure that the most specialized version of
swapis used (including user-defined overloads or specializations), programmers currently perform what's termed as a "two-way dance". This involves:- Bringing
std::swapinto the current scope withusing std::swap; - Calling
swapunqualified (swap(x, y);), allowing argument-dependent lookup (ADL) to find the most appropriate version ofswap, whether it's in thestdnamespace or a user-defined version in another namespace.
- Bringing
Simplification in C++17
- New Approach: With C++17, this process is simplified. Programmers can directly call
std::swap(x, y);without needing to perform the "two-way dance". This change is made possible by enhancements in the language, such as inline variables, that allow for more efficient resolution of function calls. - Benefits:
- Flexibility in Calling
swap: Programmers can callswapeither qualified withstd::or unqualified. The compiler is able to resolve to the most appropriate version ofswapfor the types ofxandy. - Reduction in Namespace Pollution: This approach discourages the practice of placing custom implementations in the
stdnamespace to ensure they are picked up by ADL. Instead, custom implementations can reside in their appropriate namespaces while still being discoverable through ADL.
- Flexibility in Calling
Implications for Library Authors and Programmers
- Simpler Generic Code: Authors of generic code benefit significantly from this change, as it eliminates the need for boilerplate code to ensure the correct version of
swapis used. This results in cleaner, more maintainable code. - Standard Library Development: For developers of the C++ Standard Library, this simplification reduces complexity and potential for errors in library implementation, making the standard library more robust.
Future simpler template declaration syntax
// An auto-based syntax is under discussion:
template <class T>
auto f(T& x); // C++17 return type deduction
auto f(auto& x); // C++20? parameter type deduction
Simplified Template Syntax
- Current C++17 Feature: Return type deduction allows functions to infer the return type from the return statement, reducing boilerplate.
- Proposed for Future C++ Versions: Parameter type deduction using
autoin function parameters to automatically deduce the type, further simplifying function template declarations.
Integration with Concepts
template <class T>
requires Sortable<T> // Concepts TS -> C++20
auto f(T& x);
template <Sortable T>
auto f(T& x); // Stepanov Concepts TS - C++20
auto f(Sortable auto& x); // adjective syntax - C++20?
- Concepts Introduction: With the addition of concepts in C++20, specifying requirements on template parameters becomes possible, leading to clearer and safer template definitions.
- Constrained Declarations:
- Long Form: Utilizing a
requiresclause with concepts to constrain the template parameter. - Stepanov Form: Directly specifying the concept before the parameter type in the template declaration.
- Adjective Syntax Proposal: A more concise form,
auto f(Sortable auto& x);, combines the benefits ofautofor type deduction with concepts for parameter constraints.
- Long Form: Utilizing a
Implications and Benefits
- Simplified Declarations: These proposed changes aim to make the declaration of function templates more intuitive and less verbose, promoting clearer code and reducing the likelihood of errors.
- Enhanced Readability and Safety: By integrating concepts directly into function template declarations, developers can express intent more directly and leverage compile-time checks to ensure that template parameters meet certain criteria.
- Promotion of Modern C++ Practices: These enhancements align with the ongoing evolution of C++ towards a more modern and safer language, encouraging developers to adopt new features and idioms.
Considerations
- Compatibility and Adoption: As with any new language feature, the adoption rate and impact on existing codebases will depend on compiler support, developer education, and perceived benefits versus the effort to refactor existing code.
- Standardization Process: The proposals mentioned are subject to discussion, refinement, and approval by the C++ standards committee. The final form and availability of these features in C++20 or future versions remain to be seen.
Where to go from here?
Types of Templates
- Class Templates: The foundation of generic programming in C++, allowing types to be parameters in class definitions.
- Alias Templates (since C++11): Enable the creation of template aliases, simplifying the syntax for complex template types.
- Variable Templates (since C++14): Allow variables to be defined with generic types, facilitating the definition of constants and utility functions that work with any type.
- Templates with Multiple Parameters: Explore the interactions and capabilities when templates can take more than one type or non-type parameter.
Advanced Template Features
- Parameter Packs: Support variadic templates, which can take an arbitrary number of template arguments, enabling more flexible template definitions.
- Non-type Template Parameters: Allow values, not just types, to be passed as template parameters, broadening the scope of what templates can do.
- Template Template Parameters: Templates that take other templates as parameters, offering a higher level of abstraction and reuse.
C++17 Enhancements
- Class Template Argument Deduction (CTAD): Simplifies the use of class templates by allowing the compiler to deduce the template arguments from constructor arguments, reducing boilerplate code.
Template Metaprogramming
- Template Metaprogramming: A technique that leverages templates to perform computations at compile-time, resulting in zero-runtime overhead abstractions.