This article has multiple issues. Please helpimprove it or discuss these issues on thetalk page.(Learn how and when to remove these messages) (Learn how and when to remove this message)
|
Templates are a feature of theC++ programming language that allowfunctions andclasses to operate withgeneric types. This allows a function or classdeclaration to reference via a genericvariable another different class (built-in or newly declareddata type) without creating a full declaration for each of these different classes.
In plain terms, a templated class or function would be the equivalent of (before "compiling") copying and pasting the templated block of code where it is used, and then replacing the template parameter with the actual one. For this reason, classes employing templated methods place the implementation in the headers (*.h files) as no symbol could be compiled without knowing the type beforehand.
TheC++ Standard Library provides many useful functions within a framework of connected templates.
Major inspirations for C++ templates were the parameterized modules provided by the languageCLU and the generics provided byAda.[1]
There are three kinds of templates:function templates,class templates and, sinceC++14,variable templates. SinceC++11, templates may be eithervariadic or non-variadic; in earlier versions of C++ they are always non-variadic.
C++ Templates areTuring complete.[2]
Afunction template behaves like a function except that the template can accept arguments of various types, enabling type-generic behavior (see example). In other words, a function template represents a family of functions. The format for declaring function templates with type parameters is:
template<classIdentifier>Declaration;template<typenameIdentifier>Declaration;
Both expressions have the same meaning and behave in exactly the same way. The latter form was introduced to avoid confusion,[3] since a type parameter need not be a class until C++20. (It can be a basic type such asint ordouble.)
For example, the C++ Standard Library contains the function templatemax(x, y) which returns the larger ofx andy. That function template could be defined like this[4]:
template<typenameT>[[nodiscard]]constexprT&max(constT&a,constT&b)noexcept{returna<b?b:a;}
This single function definition works with many data types. Specifically, it works with all data types for which< (the less-than operator) is defined and returns a value with a type convertible tobool. The usage of a function template saves space in the source code file in addition to limiting changes to one function description and making the code easier to read.
An instantiated function template usually produces the same object code, though, compared to writing separate functions for all the different data types used in a specific program. For example, if a program uses both anint and adouble version of themax() function template above, thecompiler will create an object code version ofmax() that operates onint arguments and another object code version that operates ondouble arguments.[citation needed] The compiler output will be identical to what would have been produced if the source code had contained two separate non-templated versions ofmax(), one written to handleint and one written to handledouble.
Here is how the function template could be used:
importstd;intmain(){// This will call max<int> by implicit argument deduction.std::println("{}",std::max(3,7));// This will call max<double> by implicit argument deduction.std::println("{}",std::max(3.0,7.0));// We need to explicitly specify the type of the arguments;// although std::type_identity could solve this problem...std::println("{}",max<double>(3,7.0));}
In the first two cases, the template argumentT is automatically deduced by the compiler to beint anddouble, respectively. In the third case automatic deduction ofmax(3, 7.0) would fail because the type of the parameters must in general match the template arguments exactly. Therefore, we explicitly instantiate thedouble version withmax<double>().
This function template can be instantiated with anycopy-constructible type for which the expressiony < x is valid. For user-defined types, this implies that the less-than operator (<) must beoverloaded in the type.
SinceC++20, usingauto orconcept auto in any of the parameters of afunction declaration, that declaration becomes anabbreviated function template declaration.[5] Such a declaration declares a function template and one invented template parameter for each placeholder is appended to the template parameter list:
// equivalent to:// template <typename T>// void f1(T x);voidf1(autox);// equivalent to (if Concept1 is a concept):// <Concept1 T>// void f2(T x);voidf2(Concept1autox);// equivalent to (if Concept2 is a concept):// template <Concept2... Ts>// void f3(Ts... xs)voidf3(Concept2auto...xs);// equivalent to (if Concept2 is a concept):// template <Concept2 T>// void f4(T... xs);voidf4(Concept2autoxs,...);// equivalent to (if Concept3 and Concept4 are concepts):// template <Concept3 T, Concept4 U>// f5(const T* t, U& u);voidf5(constConcept3auto*t,Concept4auto&u);
Constraining themax() using concepts could look something like this:
usingstd::totally_ordered;// in typename declaration:template<totally_orderedT>[[nodiscard]]constexprTmax(Tx,Ty)noexcept{returnx<y?y:x;}// in requires clause:template<typenameT>requirestotally_ordered<T>[[nodiscard]]constexprTmax(Tx,Ty)noexcept{returnx<y?y:x;}
A class template provides a specification for generating classes based on parameters. Class templates are generally used to implementcontainers. A class template is instantiated by passing a given set of types to it as template arguments.[6] The C++ Standard Library contains many class templates, in particular the containers adapted from theStandard Template Library, such asvector.
In C++14, templates can be also used for variables, as in the following example:
template<typenameT>constexprTPI=T{3.141592653589793238462643383L};// (Almost) from std::numbers::pi
Although templating on types, as in the examples above, is the most common form of templating in C++, it is also possible to template on values. Thus, for example, a class declared with
template<intK>classMyClass;
can be instantiated with a specificint.
As a real-world example, thestandard library fixed-sizearray typestd::array is templated on both a type (representing the type of object that the array holds) and a number which is of typestd::size_t (representing the number of elements the array holds). To create a classArray equivalent tostd::array, it can be declared as follows:
template<classT,size_tN>structArray;
and an array of sixchars might be declared:
Array<char,6>myArray;
When a function or class is instantiated from a template, aspecialization of that template is created by the compiler for the set of arguments used, and the specialization is referred to as being a generated specialization.
Sometimes, the programmer may decide to implement a special version of a function (or class) for a given set of template type arguments which is called an explicit specialization. In this way certain template types can have a specialized implementation that is optimized for the type or a more meaningful implementation than the generic implementation.
Explicit specialization is used when the behavior of a function or class for particular choices of the template parameters must deviate from the generic behavior: that is, from the code generated by the main template, or templates. For example, the template definition below defines a specific implementation ofmax() for arguments of typeconst char*:
importstd;template<>[[nodiscard]]constexprconstchar*max(constchar*a,constchar*b)noexcept{// Normally, the result of a direct comparison// between two C strings is undefined behaviour;// using std::strcmp makes defined.returnstd::strcmp(a,b)>0?a:b;}
C++11 introducedvariadic templates, which can take a variable number of arguments in a manner somewhat similar tovariadic functions such asstd::printf.
usingstd::format_string;usingstd::ofstream;enumclassLevel{...};ofstreamlogFile{"logfile.txt"};template<typename...Args>voidlog(constformat_string<Args...>&fmt,Args&&...args){std::println(logFile,fmt,args...);}
Because only C-style variadic parameters are supported in C++, the only way to get type-safe variadic functions (like inJava is through variadic templates.
C++11 introduced template aliases, which act like parameterizedtypedefs.
The following code shows renamingstd::map toTreeMap andstd::unordered_map toHashMap, as well as creating an aliasStringHashMap forstd::unordered_map<K, std::string>. This allows, for example,StringHashMap<int> to be used as shorthand forstd::unordered_map<int, std::string>.
usingString=std::string;// allowing optional specialization of hash functions, allocators, etc.template<typenameK,typenameV,typenameCompare=std::less<K>,typenameAlloc=std::allocator<std::pair<constK,T>>>usingTreeMap=std::map<K,V,Compare,Alloc>;template<typenameK,typenameV,typenameHashFn=std::hash<K>,typenameKeyEq=std::equal_to<K>,typenameAlloc=std::allocator<std::pair<constK,T>>>usingHashMap=std::unordered_map<K,V,HashFn,KeyEq,Alloc>;// or, only allowing K and V to be specialized:template<typenameK,typenameV>usingTreeMap=std::map<K,V>;template<typenameK,typenameV>usingHashMap=std::unordered_map<K,V>;// Defining StringHashMap<K> = HashMap<K, String>template<typenameK>usingStringHashMap=HashMap<K,String>;StringHashMap<int>myMap=/* something here... */;
SinceC++20, templates can be constrained similarly togenerics wildcards inJava orC# andRustwhere clauses. This is done usingconcepts, which represent a set of boolean predicates evaluated at compile time.
For example, this code defines a concept representing an upper bound on inheritance. A class satisfies this concept if it inherits fromPlayer, and classes that do not cannot be used as the template parameter inprocessListOfPlayers().
importstd;usingstd::is_base_of_v;usingstd::vector;classPlayer{// ...};template<typenameT>conceptExtendsPlayer=is_base_of_v<Player,T>;// T is required to be a type whose inheritance upper bound is Player,// blocking any type that does not inherit from Playertemplate<ExtendsPlayerT>voidprocessListOfPlayers(vector<T>players){// ...}
InC++03, "exported templates" were added to C++.[7] These were later removed inC++11, due to very few compilers actually supporting the feature.[8] The only compiler known to support exported templates wasComeau C/C++. Among the cited reasons for removal were:
An "exported template" is essentially a class template whose static data members and non-inline methods are exported. It must be marked by the keywordexport. What distinguishes an "exported template" is the fact that it does not need to be defined in atranslation unit that uses the template.[9][10] For example (in C++03):
File1.cpp:
#include<iostream>staticvoidtrace(){std::cout<<"File 1"<<std::endl;}exporttemplate<typenameT>Tmin(constT&x,constT&y);intmain(){trace();std::cout<<min(2,3)<<std::endl;}
File2.cpp:
#include<iostream>staticvoidtrace(){std::cout<<"File 2"<<std::endl;}exporttemplate<typenameT>Tmin(constT&x,constT&y){trace();returna<b?a:b;}
With the introduction ofmodules inC++20, the keywordexport was re-added to C++. This re-allowed declarations like this:
importstd;usingstd::is_base_of_v;exportclassAtom{// ...};exporttemplate<typenameT>conceptExtendsAtom=is_base_of_v<Atom,T>;exporttemplate<ExtendsAtomInstance,typename...Bases>classCluster:publicBases...{private:Instancex;public:explicitCluster(Instancex,Bases&&...bases):Bases(bases)...,x{x}{}// ...};
The compilation speed benefits intended to be offered by exported templates are offered by modules anyway, making the feature essentially obsolete and superseded by modules.
Initially, the concept of templates was not included in some languages, such asJava andC# 1.0.Java's adoption of generics mimics the behavior of templates, but is technically different. C# added generics (parameterized types) in.NET 2.0. The generics in Ada predate C++ templates.
Although C++ templates, Java generics, and.NET generics are often considered similar, generics only mimic the basic behavior ofC++ templates.[11] Some of the advanced template features utilized by libraries such asBoost andSTLSoft, and implementations of the STL, fortemplate metaprogramming (explicit or partial specialization, default template arguments, template non-type arguments, template template arguments, ...) are unavailable with generics.
In C++ templates, compile-time cases were historically performed by pattern matching over the template arguments. For example, the template base class in the Factorial example below is implemented by matching 0 rather than with an inequality test, which was previously unavailable. However, the arrival in C++11 of standard library features such asstd::conditional has provided another, more flexible way to handle conditional template instantiation.
// Inductiontemplate<unsignedintN>structFactorial{staticconstexprunsignedintvalue=N*Factorial<N-1>::value;};// Base case via template specialization:template<>structFactorial<0>{staticconstexprunsignedintvalue=1;};
With these definitions, one can compute, say 6! at compile time using the expressionFactorial<6>::value.
Alternatively,constexpr in C++11 /if constexpr in C++17 can be used to calculate such values directly using a function at compile-time:
template<unsignedintN>[[nodiscard]]constexprunsignedintfactorial()noexcept{ifconstexpr(N<=1){return1;}else{returnN*factorial<N-1>();}}
Because of this, template meta-programming is now mostly used to do operations on types.