You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Templates are C++ entities that use the template keyword to define and
use the typename or the class keyword to speficy a generic type .
Template parameters cannot be references or pointers.
Template parameters must be either types (e.g., class T, typename T) or
values (e.g., int N).
All type parameters can be bound to default types, and all non-type
parameters can be bound to default values.
Templates empower developers to write generic and reusable code. By
enabling functions and classes to operate on arbitrary data types,
templates facilitate enhanced flexibility and type safety in programming.
They do not exist, and the compiler does not create them, and some
compilers do not detect its syntaxuntil they are called. At compile
time, the compiler create and detect them.
Because templates exist and are compiled only for specific types when
instantiated, they can be declared and defined in header files.
For template classes or functions, the full definition must be visible
before instantiation.
Forward declarations are generally not practical for templates unless
you use explicit specialization or separate declarations for inline
implementations.
Order of Parameters in the Parameter List
For function templates, there is no specific order for parameters in
the parameter list. Type or non-type parameters, whether they have default
types or values, and parameter packs can be placed anywhere in the list.
However, for class templates, there is a specific order:
All parameters without default types or values must be placed at the
beginning of the parameter list.
All parameters with default types or values must be specified at the end of
the parameter list, but before any parameter packs.
All parameter packs must be placed at the end of the parameter list.
Better orders:
Type parameters without default types, non-type parameters without
default values, type parameters with default types, non-type parameters
with default values, and parameter packs.
Non-type parameters without default values, type parameters without default
types, non-type parameters with default values, type parameters with
default types, and parameter packs.
Limitations of Template Default Types and Values
Order of parameters:
It is not permissible to omit a parameter with a default value if one
intends to provide values for later parameters.
Dependent names:
Default template parameters cannot depend on other template parameters.
A template parameter cannot be utilized to define the default value of
another template parameter.
Ambiguity in overloading:
The use of default template parameters in function overloading may lead to
ambiguity.
If multiple templates can potentially match a call due to the presence of
default arguments, the compiler may encounter difficulty in determining
which template to invoke.
Inheritance and template defaults:
In derived template classes, the default arguments inherited from
the base class cannot be modified.
It is necessary to explicitly specify the types in the derived class.
Specialization limitations:
Template specializations are unable to alter default template
parameters.
If a specialization employs a different type or value, it must explicitly
define those parameters.
Explicit Instantiation
The explicit instantiation refers to the deliberate and formal creation of a
specific instance of a template with a particular type, as specified by the
programmer.
It works similarly to declaring a template with specific types and then
using it.
This is done using the template keyword followed by the instantiation
of the template with
the desired type.
This mechanism is primarily used to control when and where a template is
instantiated, particularly in larger codebases where managing template
definitions and instances can become complex.
Implicit Instantiation (Common Instantiation)
The implicit instantiation refers to the automatic creation of a template
instance by the compiler when a template is used with a specific type,
without requiring explicit instantiation by the programmer.
It works similarly to using a template directly with specific types,
without any prior declaration.
Function Templates
Explanation
Function templates allow for the definition of functions that can
operate with any data type.
Function templates in C++ allow automatic type deduction for the template
parameters, which means that the type of the argument passed to the function
can be used to deduce the corresponding template type T.
This capability reduces code duplication and enhances type safety, as
the compiler can automatically generate type-specific implementations based
on the provided template parameters.
// Usage syntax, implicit instantitaion syntax, automatic type deduction.
Type result = funcName( ... );
// Usage syntax, implicit instantitaion syntax.
Type result = funcName<>( ... );
// Usage syntax, implicit instantitaion syntax.
Type result = funcName< ... >( ... );
Class Templates or Struct Templates
Explanation
Class templates or struct templates enable the creation of classes or
structs that can manage various data types.
This feature is particularly advantageous for implementing data
structures such as lists, stacks, and queues, where the type of data
may vary.
Syntax
// With explicit instantiation.namespaceSpaceName {
// Definition Syntax.template< classT, ... > classClassName {
RetType funcName( ... ) {
// Do something.
};
};
}; // namespace SpaceName.// Usage syntax, instantiation syntax./* template class ClassName<int>; // error: class template ClassName not visible in the global namespace.using SpaceName::ClassName;template class ClassName<int>; // error: explicit instantiation outside of the namespace of the template. */template classSpaceName::ClassName< Type, ... >; // OK: explicit instantiation.template RetType SpaceName::ClassName< Type, ... >::funcName( ... ); // OK: explicit instantiation.
// With implicit instantiation.// Definition Syntax.template< classT, ... > structStructName
{
};
// Usage syntax, instantiation syntax.template structStructName< Type, ... >; // Explicit instantiation of StructName< Type >.
StructName< Type, ... > obj; // Implicit instantiation of StructName< Type >.
Template Specialization
Explanation
Template specialization allows for the definition of a specific
implementation of a template for a particular data type to meet specific
conditions.
However, they do not allow more than one specialization for the same type
parameters. The compiler will throw an error if multiple
specializations that match the same signature are declared.
This feature is useful when the generic implementation requires adjustment
for certain types to enhance functionality or performance.
Full specialization
Explanation
In full specialization, it is imperative to specify all template
parameters explicitly.
For instance, if your template accepts two parameters, both must be
explicitly defined in the specialization.
Syntax
// Definition syntax.template< para_list1 > classClassName {
// Implementation.
};
// Full specialization for `arg_type_list2`.template<> classClassName< arg_type_list2 > {
// Another implementation.
};
// Usage syntax.
ClassName< arg_type_list1 > obj1; // Utilizes the generic version.
ClassName< arg_type_list2 > obj2; // Utilizes the specialized version.
Variadic templates are templates that contain at least one parameter
pack.
A template parameter pack is a template parameter that accepts zero
or more template arguments (non-types, types, or templates).
The common way to store the arguments of a template parameter pack
and access them is by using std::tuple and std::get.
This feature is beneficial for functions that need to handle a flexible
number of arguments, thereby enhancing versatility.
For a function template, multiple parameter packs can be defined,
while a class template can only have one.
For a function template, a template parameter pack can automatically
deduce types and the types are not explicitly specified when the function
is called.
However, for a class template (such as std::tuple or std::vector),
the types must be explicitly specified because template parameter packs
are typically not deduced in class templates.
Common Syntax
#include<iostream>
#include<tuple>// Be mindful of the Ellipsis.template< typename... Args > classClassName {
public:// explicit ClassName( Args&... args ):// explicit ClassName( Args&&... args ):explicitClassName( Args... args ):
_mem( args... ), _mem_ptr( &args... ) {
// Method to print all arguments in order without adding any extra characters.
( std::cout << ... << args ) << std::endl;
// Method to print all arguments'addresses in order without adding any extra characters.
( std::cout << ... << &args ) << std::endl;
// Method to print all ++arguments in order without adding any extra characters.
( std::cout << ... << ++args ) << std::endl;
// Method to add all arguments in order and print the result.
std::cout << ( ... + args ) << std::endl;
// Method to multiple all arguments in order and print the result.
std::cout << ( ... * args ) << std::endl;
// Method to && all arguments in order and print the result.
std::cout << ( ... && args ) << std::endl;
// Method to || all arguments in order and print the result.
std::cout << ( ... || args ) << std::endl;
};
// Method to get an element by index.template< std::size_t Index > decltype( auto ) get() {
return std::get< Index >( _mem );
};
// Others.private:
std::tuple< Args... > _mem; // Store arguments in a tuple.
std::tuple< Args*... > _mem_ptr; // Store arguments's addresses in a tuple.// Others
};
intmain() {
ClassName< ArgType1, ArgType2, ArgType3, /* and so on */ > obj(
arg1, arg2, arg3, /* and so on */ );
// Access specific elements by index.
std::cout << "First element: " << obj.get< 0 >() << std::endl; // arg1.
std::cout << "Second element: " << obj.get< 1 >() << std::endl; // arg2.
std::cout << "Third element: " << obj.get< 2 >() << std::endl; // arg3.// Others.return0;
};
Template parameters can include non-type parameters, which are constants
of integral or enumeration types, pointers, references, or even certain types
of non-type template parameters.
This feature allows you to create templates that are more flexible and
capable of handling specific values alongside types, enabling
additional control over template behavior and structure.
Syntax
// Definition syntax.template< typename T, ..., Type N, ... > classClassName {
...;
};
Class templates, function templates, and non-template functions (typically
members of class templates) might be associated with a constraint, which
specifies the requirements on template arguments, which can be used to
select the most appropriate function overloads and template
specializations.
Named sets of such requirements are called concepts. Each concept is
a predicate, evaluated at compile time, and becomes a part of the interface
of a template where it is used as a constraint:
It is a principle that allows template substitutions to fail and
may still result in a compilation error — but it is not treated
as an error in the context of template instantiation.
This is useful for enabling or disabling templates based on certain
conditions.
std::enable_if is a common tool for implementing SFINAE, there are
other techniques and constructs that can also be used, such as function
overloading, template specialization, type traits, constraints and concepts
in templates, and more.
If a function template cannot be instantiated due to type mismatches, the
compiler simply ignores that overload instead of producing an error.
If a template specialization cannot be matched, it will not result in an
error but rather allow the compiler to try other template.
Declaration syntax:
template< bool B, classT = void > structenable_if; // It has a public member typedef `type`, equal to `T`.
Its implementation syntax:
template< bool B, classT = void > structenable_if {}; // Primary template.// Partial specialization for the case when B is true.template< classT > structenable_if< true, T > {
using type = T;
};
// The definition of `std::enable_if`.template< bool Cond, classT = void > structenable_if {};
template< classT > structenable_if< true, T > {
typedef T type;
};
// With `std::enable_if`.
#include<type_traits>// In this, `TypeTrait` is a template function (or class or structure,// even with the `constexpr` keyword) in the header `<type_traits>`,// for example, `std::is_integral`.template< typename T, ... >
typename std::enable_if< TypeTrait< T, ... >::value >::type funcName( T para, ... ) {
// If T, ... meet specific conditions.// Do something.
};
template< typename T, ... >
typename std::enable_if< !TypeTrait< T >::value >::type funcName( T para, ... ) {
// If T is not a specific type.// Do something.
};
// With `std::enable_if`.
#include<type_traits>// In this, `TypeTrait` is a template function (or class or structure,// even with the `constexpr` keyword) in the header `<type_traits>`,// for example, `std::is_integral`.template< typename T,
typename std::enable_if< TypeTrait< T, ... >::value >::type,
... >
RetType funcName( T para, ... ) {
// If T, ... meet specific conditions.// Do something.
};
template< typename T,
typename std::enable_if< !TypeTrait< T >::value >::type,
... >
RetType funcName( T para, ... ) {
// If T is not a specific type.// Do something.
};
// With constraints and concepts.
#include<type_traits>// In this, `TypeTrait` is a template function (or class or structure,// even with the `constexpr` keyword) in the header <type_traits>,// for example, std::is_integraltemplate< typename T, ... > concept TempName = TypeTrait< T, ... >;
template< TempName T, ... > RetType funcName( T para, ... ) {
// This function is only enabled for specific conditions
};
Type Traits
Explanation
Type traits are a set of template classes (or structures or functions
even with the constexpr keyword) provided by the C++ Standard Library that
allow you to query and manipulate type information at compile time.
They can be combined with various techniques used to complete
evaluations at compile time, such as static const, constexpr,
templates, std::enable_if, static_assert, and more.
All type traits are in the header <type_traits>.
Syntax
// Using templates and `static_assert` to implement a type trait.template< typename T > structIsPointer {
staticconstbool val = false; // Default case.
};
template< typename T > structIsPointer< T* > {
staticconstbool val = true; // Specialization for pointers.
};
// Usage.static_assert( IsPointer< int* >::val, "Should be a pointer type" );
static_assert( !IsPointer< int >::val, "Should not be a pointer type" );
// Using a type trait with `static_assert`.
#include<type_traits>// In this, `TypeTrait` is a template function (or class or structure,// even with the `constexpr` keyword) in the header <type_traits>,// for example, `std::is_integral`.template< typename T, ... > RetType funcName( T t, ... ) {
static_assert( TypeTrait< T >::value, "T must be a specific type" );
// Do something.
};
std::forward and Universial References (Forward References)
std::forward
std::forward is a utility function that is used for perfect
forwarding of function arguments, ensuring that their value categories
(whether they are lvalues or rvalues) are preserved during the forwarding
process.
It returns an rvalue reference to obj_name if obj_name is not an lvalue
reference.
If obj_name is an lvalue reference, the function returns obj_name without
modifying its type.
std::forwardacts as a transfer station that preserves the original
value category (whether it is an lvalue or an rvalue) of the argument it
forwards.
Its header file is <utility>.
Universial References (Forward References)
A universal reference is a special type of reference in C++ that can
bind to both lvalues (temporary object) and rvalues (persistent object).
It is also called a forwarding reference in modern C++ terminology.
The term "universal reference" is particularly used in the context of
template functions.
A universal reference typically appears in a template function parameter when
the type is declared as T&& but is neither a rvalue reference type nor a
lvalue reference type.
Only when automatic template type deduction is used (e.g., in
function templates or templated constructors), the compiler deduces the
universal reference depending on the value category of the argument passed.
When an lvalue is passed, T is deduced as Type&, so the universal
reference becomes Type& &&, which is collapsed to Type&, obeying
reference collapsing rules.
When an rvalue is passed, T is deduced as Type, and the universal
reference becomes Type&&.
Universal references are often used in perfect forwarding, where you want
to forward the arguments exactly as received, keeping their value category
intact.
If T is explicitly specified or in a non-deduced context, T&& is a
pure rvalue reference.
// An usage example.
#include<iostream>
#include<utility>// For std::forward// Function to demonstrate perfect forwardingtemplate< typename T > voidwrapper( T&& arg ) {
// Forward the argument to another function// This preserves whether arg is an lvalue or rvalueprocess( std::forward< T >( arg ) );
};
// Helper function to handle both lvalues and rvaluesvoidprocess( int& x ) { std::cout << "Lvalue processed: " << x << std::endl; };
voidprocess( int&& x ) { std::cout << "Rvalue processed: " << x << std::endl; };
intmain() {
int x = 42;
wrapper( x ); // Lvalue passed, so it will call process(int&)wrapper( 10 ); // Rvalue passed, so it will call process(int&&)
};