STL (chapter18) we'vealready used these constructs, commonly known as thetemplate mechanism.The template mechanism allows us to specify classes and algorithms, fairlyindependently of the actual types for which the templates are eventually goingto be used. Whenever the template is used, the compiler generates code that istailored to the particular data type(s) used with the template. This code isgenerated atcompile-time from the template's definition. The piece ofgenerated code is called aninstantiation of the template.
In this chapter the syntactic peculiarities of templates are covered. Thenotions oftemplate type parameter,template non-type parameter, andfunction template are introduced and several examples of templates areprovided (both in this chapter and in chapter26). Templateclasses are covered in chapter22. For good reasons variadicfunctions are deprecated inC++. However,variadic templates tell us acompletely different story, and variadic templates are perfectlyacceptable. Both function- and class-templates can be defined as variadictemplates. Both forms are covered in section22.5.
Templates already offered by the language include the abstract containers(cf. chapter12); thestring (cf. chapter5);streams (cf. chapter6); and the generic algorithms (cf. chapter19). So, templates play a central role in present-dayC++, andshould not be considered an esoteric feature of the language.
Templates should be approached somewhat similarly as generic algorithms:they're away of life; aC++ software engineer should actively lookfor opportunities to use them. Initially, templates may appear to be rathercomplex and you might be tempted to turn your back on them. However, over timetheir strengths and benefits are more and more appreciated. Eventually you'llbe able to recognize opportunities for using templates. That's the time whereyour efforts should no longer focus on constructing ordinary functions and classes (i.e., functions orclasses that are not templates), but on constructing templates.
This chapter starts by introducingfunction templates. The emphasis is onthe required syntax. This chapter lays the foundation upon which the otherchapters about templates are built.
add expects twoType arguments andreturns their sum: Type add(Type const &lhs, Type const &rhs) { return lhs + rhs; }Note how closely the above function's definition follows its description.It receives two arguments, and returns its sum. Now consider what would happenif we defined this function for, e.g.,int values. We would write:
int add(int const &lhs, int const &rhs) { return lhs + rhs; }So far, so good. However, were we to add two doubles, we would overloadthis function:
double add(double const &lhs, double const &rhs) { return lhs + rhs; }There is no end to the number of overloaded versions we might be forced toconstruct: an overloaded version forstring, forsize_t, for .... Ingeneral, we would need an overloaded version for every type supportingoperator+ and a copy constructor. All these overloaded versions ofbasically the same function are required because of the strongly typed natureofC++. Because of this, a truly generic function cannot be constructedwithout resorting to thetemplate mechanism.
Fortunately, we've already seen an important part of a templatefunction. Our initial functionadd actually is an implementation of such afunction although it isn't a full template definition yet. If we gave thefirstadd function to the compiler, it would produce an error messagelike:
error: `Type' was not declared in this scope error: parse error before `const'
And rightly so, as we failed to defineType. The error is preventedwhen we changeadd into a full template definition. To do this, we lookat the function's implementation and decide thatType is actually aformal typename. Comparing it to the alternate implementations, it isclear that we could have changedType intoint to get the firstimplementation, and intodouble to get the second.
The full template definition allows for this formal nature of theType typename. Using the keywordtemplate, we prefix one line toour initial definition, obtaining the following function templatedefinition:
template <typename Type> Type add(Type const &lhs, Type const &rhs) { return lhs + rhs; } In this definition we distinguish:template, starting a template definition ordeclaration.template. This is alist containing one or more comma-separated elements. This angle bracketenclosed list is called thetemplate parameter list. Template parameter lists using multipleelements could look like this:typename Type1, typename Type2
Type. It is a formal type name, comparable to a formal parameter name in afunction's definition. Up to now we've only encountered formal variable nameswith functions. Thetypes of the parameters were always known by the timethe function was defined. Templates escalate the notion of formal names onestep further up the ladder. Templates allow type names to be formalized,rather than just the variable names themselves. The fact thatType is aformal type name is indicated by the keywordtypename, prefixed toType in the template parameter list. A formal type name likeType isalso called atemplate type parameter. Template non-type parameters alsoexist, and are shortly introduced.Other texts onC++ sometimes use the keywordclass where we usetypename. So, in other texts template definitionsmight start with a line like:
template <class Type>
In theC++ Annotations the use oftypename overclass is preferred,reasoning that a template type parameter is, after all, a type name (someauthors preferclass overtypename; in the end it's a matter oftaste).
template keyword and the template parameter list is calledthetemplate header.add template definition.Type const ¶meters. This has the usual meaning: the parameters are references toType objects or values that will not be modified by the function.add's body, it is clear thatoperator+ is used, as well as a copyconstructor, as the function returns a value. This allows us to formulate thefollowing restrictions for the formal typeType as used by ouraddfunction template:Type should supportoperator+Type should support a copy constructorType could be astring, it could never beanostream, as neitheroperator+ nor the copy constructor areavailable for streams. template <typename Type> Type add(Type const &lhs, Type const &rhs) { return lhs + rhs; } Look again atadd's parameters. By specifyingType const &rather thanType superfluous copying is prevented, at the same timeallowing values of primitive types to be passed as arguments to thefunction. So, whenadd(3, 4) is called,int{4} is assigned toType const &rhs. In general, function parameters should be defined asType const & to prevent unnecessary copying. The compiler is smart enoughto handle `references to references' in this case, which is something thelanguage normally does not support. For example, consider the followingmain function (here and in the following simple examples it is assumedthat the template and the required headers and namespace declarations havebeen provided): int main() { size_t const &var = size_t{ 4 }; cout << add(var, var) << '\n'; }Herevar is a reference to a constantsize_t. It is passed asargument toadd, thereby initializinglhs andrhs asTypeconst & tosize_t const & values. The compiler interpretsType assize_t. Alternatively, the parameters might have been specified usingType &, rather thanType const &. The disadvantage of this (non-const)specification being that temporary values cannot be passed to the functionanymore. The following therefore fails to compile:
int main() { cout << add(string{ "a" }, string{ "b" }) << '\n'; }Here, astring const & cannot be used to initialize astring &.Hadadd definedType && parameters then the above program would havecompiled just fine. In addition the following example correctly compiles asthe compiler decides thatType apparently is astring const:
int main() { string const &s = string{ "a" }; cout << add(s, s) << '\n'; }What can we deduce from these examples?
Type const& parameters to prevent unnecessary copying.Type const &:| Provided argument: | Actually used Type: |
size_t const | size_t |
size_t | size_t |
size_t * | size_t * |
size_t const * | size_t const * |
As a second example of a function template, consider the followingfunction template:
template <typename Type, size_t Size> Type sum(Type const (&array)[Size]) { Type tp{}; // note: the default constructor must exist. for (size_t idx = 0; idx < Size; idx++) tp += array[idx]; return tp; }This template definition introduces the following new concepts andfeatures:
size_t. Template parametersof specific (i.e., non-formal) types used in template parameter lists arecalledtemplate non-type parameters. A template non-type parameter defines the type of aconstant expression, which must be known by the time the template isinstantiated and which is specified in terms of existing types, such as asize_t.Type const (&array)[Size]
This parameter definesarray as a reference to an array havingSize elements of typeType that may not be modified.
Type andSize areused.Type is of course the template's type parameterType, butSize is also a template parameter. It is asize_t, whose value must beinferable by the compiler when it compiles an actual call of thesumfunction template. Consequently,Size must be aconst value. Such aconstant expression is called atemplate non-type parameter, and its typeis named in the template's parameter list.Type's concrete value, but alsoSize's value. Since thefunctionsum only has one parameter, the compiler is only able to inferSize's value from the function's actual argument. It can do so if theprovided argument is an array (of known and fixed size) rather than a pointertoType elements. So, in the followingmain function the firststatement will compile correctly but the second statement will not:int main(){ int values[5]; int *ip = values; cout << sum(values) << '\n'; // compiles OK cout << sum(ip) << '\n'; // won't compile}Type tp{} is usedto define and initializetp to a default value. Note here that no fixedvalue (like 0) is used. Also, be careful not to useType tp(), as this isadeclaration of a functiontp, expecting no arguments, and returningaType. Generally, when it is required to explicitly initialize a valuethe empty curly braces should be used. The advantage of explicitly calling thetype's constructor is primarily encountered whenType is a basictype. E.g., ifType is anint thenType tp{} initializestp tozero, whereasType tp results intp having an undefined value. But all types, even the primitive types, support default constructors(some classes may choose not to implement a default constructor, or to make itinaccessible; but most do offer default constructors). The default constructor of primitive types initializes theirvariables to 0 (orfalse). Furthermore, the statementType tp = Type()is a true initialization:tp is initialized byType's defaultconstructor, rather than usingType's copy constructor to assignType's copy totp.It's interesting to note (although not directly related to thecurrent topic) that the syntactic constructionType tp(Type())cannotbe used, even though it also looks like a proper initialization. Usually aninitializing argument can be provided to an object's definition, likestring s("hello"). Why, then, isType tp = Type() accepted, whereasType tp(Type()) isn't? WhenType tp(Type()) is used it won't resultin an error message. So we don't immediately detect that it'snot aType object's default initialization. Instead, the compiler startsgenerating error messages oncetp is used. This is caused by the fact thatinC++ (and inC alike) the compiler does its best to recognize afunction or function pointer whenever possible: thefunction prevalence rule. According to this ruleType() is (becauseof the pair of parentheses) interpreted as apointer to a functionexpecting no arguments; returning aType. The compiler will do so unlessit clearly isn't possible to do so. In the initializationType tp = Type()itcan't see a pointer to a function as aType object cannot be giventhe value of a function pointer (remember:Type() is interpreted asType (*)() whenever possible). But inType tp(Type()) itcan usethe pointer interpretation:tp is nowdeclared as afunctionexpecting a pointer to a function returning aType, withtp itselfalso returning aType. E.g.,tp could have been defined as:
Type tp(Type (*funPtr)()){ return (*funPtr)();}sum also assumes theexistence of certain public members inType's class. This timeoperator+= andType's copy constructor.Like class definitions, template definitions should not containusing directives or declarations: the template might be used in a situationwhere such a directive overrides the programmer's intentions: ambiguities orother conflicts may result from the template's author and the programmer usingdifferentusing directives (E.g, acout variable defined in thestd namespace and in the programmer's own namespace). Instead, withintemplate definitions onlyfully qualified names, including all required namespace specifications shouldbe used.
auto keyword was introduced. The keyworddecltype, related toauto, shows somewhat different behavior. Thissection concentrates ondecltype. Different fromauto, which requiresno further specifications,decltype is always followed by anexpression between parentheses (e.g.,decltype(variable)).As an initial illustration, assume we have a function defining a parameterstd::string const &text. Inside the function we may encounter thefollowing two definitions:
auto scratch1{text}; decltype(text) scratch2 = text;Withauto the compiler deduces a plain type, soscratch1 is astring, and copy construction is used to initialize it from`text'.
Now considerdecltype:decltype determinestext's type:string const &, which is thereupon used asscratch2's type:stringconst &scratch2, referring to whatever stringtext refers to. This isdecltype's standard behavior: when provided with a variable's name,it is replaced by that variable's type.
Alternatively, an expression can be specified when usingdecltype. Ofcourse, a variable is an expression by itself, but in the context ofdecltype we define an `expression' as any expression that is more complexthan just a plain variable specification. But it may be as simple as(variable): the name of a variable between parentheses.
When an expression is used, the compiler determines whether a reference couldbe appended to the expression's type. If so,decltype(expression) isreplaced by the type of such an lvalue reference (so you getexpression-type &). If not,decltype(expression) is replaced by theexpression's plain type.
Here are some examples:
int *ptr; decltype(ptr) ref = ptr; // decltype's argument is a plain variable, and so // ptr's type is used: int *ref = ptr. // decltype(ptr) is replaced by int *. // (resulting in two warnings about not-initialized/used variables). int *ptr; decltype( (ptr) ) ref = ptr; // decltype's argument is an expression, and so // int *&ref = ptr is used. // decltype( (ptr) ) is replaced by int *&. int value; decltype(value + value) var = value + value; // decltype's argument is an expression, and so the compiler tries // to replace decltype(...) by int & (int &var = value + value) // since value + value is a temporary, var's type cannot be int & // and so decltype(...) is replaced by int // (i.e., value + value's type) string lines[20]; decltype(lines[0]) ref = lines[6]; // decltype's argument is an expression, so // string &ref = lines[6] is used. // decltype(...) is replaced by string & string &&strRef = string{}; decltype(strRef) ref = std::move(strRef); // decltype's argument is a plain variable so the variable's // type is used: string &&ref = std::move(strRef). // decltype(...) is replaced by string && string &&strRef2 = string{} decltype((strRef2)) ref2 = strRef2; // decltype's argument is an expression, so // string && &ref = strRef is used. This automatically becomes // string &ref = strRef which is OK // decltype is replaced by string &.In addition to this,decltype(auto) specifications can be used, inwhich casedecltype's rules are applied toauto. So,auto is usedto determine the type of the initializing expression. Then, if theinitializing expression is a mere variable, then the expression's type isused. Otherwise, if a reference can be added to the expression's type thendecltype(auto) is replaced by a reference to the expression's type. Hereare some examples:
int *ptr; decltype(auto) ptr2 = ptr; // auto produces ptr's type: int *, ptr is a plain variable, so // decltype(auto) is replaced by int * int value; decltype(auto) ret = value + value; // auto produces int, value + value is an expression, so int & is // attempted. However, value + value cannot be assigned to a // reference so the expression's type is used: // decltype(auto) is replaced by int string lines[20]; decltype(auto) line = lines[0]; // auto produces string, lines[0] is an expression, so string & is // attempted. string &line = lines[0] is OK, so // decltype(auto) is replaced by string & decltype(auto) ref = string{} // auto produces string, string{} is an expression, so string & is // attempted. However, string &ref = string{} is not a valid // initialization, so string itself is used: // decltype(auto) is replaced by stringIn practice, thedecltype(auto) form is most often encountered withfunction templates to define return types. Have a look at the followingstruct definition (not using function templates, but illustrating the workingsofdecltype(auto)):
struct Data { vector<string> d_vs; string *d_val = new string[10]; Data() : d_vs(1) {} auto autoFun() const { return d_val[0]; } decltype(auto) declArr() const { return d_val[0]; } decltype(auto) declVect() const { return d_vs[0]; } };autoFun returnsauto. Sinced_val[0] is passed toauto,auto is deducing asstring, and the function's return type isstring;declArr returnsdecltype(auto). Sinced_val[0] is an expression, representing astring,decltype(auto) is deduced asstring &, which becomes the function's return type.declVect returnsdecltype(auto). Sinced_vs[0] is an expression, representingstring,decltype(auto) is deduced asstring &. However, sincedeclVect is also a const member, this reference should be astring const &. This is recognized bydecltype(auto), and so the function's return type becomesstring const &.If you're wondering why there's noconst indeclArr's return typewhile there is one indeclVect's return type then have a look atd_vsandd_val: both are constant in the context of their functions, butd_val, so aconst *, points to non-conststring objects. So,declArr doesnot have to return astring const &, whereasdeclVectshould return astring const &.
decltype is a tool for determining the type of anexpression. To use it an expression to whichdecltype is applied must beavailable. But what if a function template defines atypename Classtemplate parameter and the function template should use the return type of thefunctionClass::fun()? Since two classes may define membersfun havingdifferent return types, the return type to use is not immediately available.These kinds of problems are solved by using the function templatestd::declval, defined in the<utility> headerfile. This function template defines one template type parameter, and returnsan rvalue reference to an object of the template type parameter's class,without actually creating a temporary object. But since an rvalue reference isavailable, itsfun function can be called, and the return type ofthatfunction can then be produced bydecltype. There are no specificrequirements for the constructors of the class type that's passed todeclval. Specifically: it doesn't have to have a default or publicconstructor (but access rightsare used). Consider this function template:
template <typename Type> decltype(std::declval<Type>().fun()) value() { return 12.5; }The functionvalue's return type is defined as the as yet unknownType::fun's return type.
By defining two structs, both havingfun member functionsvalue'sactual return type can now be returned. This is used inmain whererespectively anint and adouble is returned, resulting in the output12 12.5:
struct Integral { int fun() const; // implementation not required }; struct Real { double fun() const; // same. }; int main() { std::cout << value<Integral>() << ' ' << value<Real>() << '\n'; } int add(int lhs, int rhs) { return lhs + rhs; }The above function may be converted to a function template:
template <typename Lhs, typename Rhs> Lhs add(Lhs lhs, Rhs rhs) { return lhs + rhs; }Unfortunately, when the function template is called as
add(3, 3.4)
the intended return type is probably adouble rather than anint. This can be solved by adding an additional template type parameterspecifying the return type but then that type must explicitly be specified:
add<double>(3, 3.4);
Usingdecltype (cf. section3.3.7) to define the return type won'twork aslhs andrhs aren't known to the compiler by the timedecltype is used. Thus the next attempt to get rid of the additionaltemplate type parameter fails to compile:
template <typename Lhs, typename Rhs> decltype(lhs + rhs) add(Lhs lhs, Rhs rhs) { return lhs + rhs; }Thedecltype-based definition of a function's return type may becomefairly complex. This complexity can be reduced by using thelate-specified return type syntax thatdoes allow the use ofdecltype to define a function's return type. It is primarily used withfunction templates but it may also be used for ordinary (non-template)functions:
template <typename Lhs, typename Rhs> auto add(Lhs lhs, Rhs rhs) -> decltype(lhs + rhs) { return lhs + rhs; }When this function is used in a statement likecout << add(3, 3.4) theresulting value will be 6.4, which is most likely the intended result, ratherthan 6. As an example how a late-specified return type may reduce thecomplexity of a function's return type definition consider the following:
template <typename T, typename U> decltype((*(T*)0)+(*(U*)0)) add(T t, U u);
Kind of hard to read? A term like (*(T*)0) defines 0, using aC cast, as a pointer to typeT and then dereferences the pointer, producing a value of typeT (even though that value itself doesn't exist as a variable). Likewise for the second term that's used in thedecltype expression. The resulting type is thereupon used asadd's return type. Using a late-specified return type we get the equivalent:
template <typename T, typename U> auto add(T t, U u) -> decltype(t+u);
which most people consider easier to understand.
The expression specified withdecltype does not necessarily use theparameterslhs andrhs themselves. In the next functiondefinitionlhs.length is used instead oflhs itself:
template <typename Class, typename Rhs> auto add(Class lhs, Rhs rhs) -> decltype(lhs.length() + rhs) { return lhs.length() + rhs; }Any variable visible at the timedecltype is compiled can be usedin thedecltype expression. It is also possible tohandle member selection through pointers to members. The following code aimsat specifying the address of a member function asadd's first argument andthen use its return value type to determine the function template's returntype. Here is an example:
std::string global{"hello world"}; template <typename MEMBER, typename RHS> auto add(MEMBER mem, RHS rhs) -> decltype((global.*mem)() + rhs) { return (global.*mem)() + rhs; } int main() { std::cout << add(&std::string::length, 3.4) << '\n'; // shows: 14.4 }<functional> header file must be included.Situations exist where the compiler is unable to infer that a reference ratherthan a value is passed to a function template. In the following example thefunction templateouter receivesint x as its argument and thecompiler dutifully infers thatType isint:
template <typename Type> void outer(Type t) { t.x(); } void useInt() { int arg; outer(arg); } Compilation will of course fail (asint values don't havexmembers) and the compiler nicely reports the inferred type, e.g.:In function 'void outer(Type) [with Type = int]': ...
Another type of error results from usingcall in the next example. Here,call is a function template expecting a function-type argument. Thefunction that's passed tocall issqrtArg, defining a reference to adouble: the variable that's passed tosqrtArg is modified bysqrtArg.
void sqrtArg(double &arg) { arg = sqrt(arg); } template<typename Fun, typename Arg> void call(Fun fun, Arg arg) { fun(arg); cout << "In call: arg = " << arg << '\n'; } The first timecall is used,call(sqrtArg, value) will not modifyvalue: the compiler infersArg to be adouble value, and hencepassesvalue by value tocall, thus preventingsqrtArg to modifymain's variable.To changemain's variablevalue the compiler must be informed thatvalue must be passed by reference. Note that we do not want to definecall's template parameter as a reference parameter, as passing arguments by value might be appropriate in other situations.
In these situations theref(arg) andcref(arg)reference wrappers should be used. They accept anargument and return their argument as a (const) reference-typed argument. Toactually changevalue it can be passed tocall usingref(value) asshown in the followingmain function:
int main() { double value = 3; call(sqrtArg, value); cout << "Passed value, returns: " << value << '\n'; call(sqrtArg, ref(value)); cout << "Passed ref(value), returns: " << value << '\n'; } /* Displays: In call: arg = 1.73205 Passed value, returns: 3 In call: arg = 1.73205 Passed ref(value), returns: 1.73205 */ enum { V1, V2, V3 };Here, theenum defines anunnamed oranonymous type.
When defining a function template, the compiler normallydeduces the types of its template type parameters from its arguments:
template <typename T> void fun(T &&t); fun(3); // T is int fun('c'); // T is charThe following, however, can also be used:
fun(V1); // T is a value of the above enum type
Withinfun aT variable may be defined, even if it's an anonymoustype:
template <typename T> void fun(T &&t) { T var(t); }Values or objects of locally defined types may also be passed as arguments to functiontemplates. E.g.,
void definer() { struct Local { double dVar; int iVar; }; Local local; // using a local type fun(local); // OK: T is 'Local' }When the compiler deduces the actual types for template type parameters itonly considers the types of the arguments that are actuallyused. Neither local variables nor the function's return value is considered inthis process. This is understandable. When a function is called the compileris only certain about the types of the function template's arguments. At thepoint of the call it definitely does not see the types of the function's localvariables. Also, the function's return value might not actually be used or maybe assigned to a variable of a subrange (or super-range) type of a deducedtemplate type parameter. So, in the following example, the compiler won't everbe able to callfun(), as it won't be able to deduce the actual type fortheType template type parameter.
template <typename Type> Type fun() // can never be called as `fun()' { return Type{}; }Although the compiler won't be able to handle a call to `fun()', itis possible to callfun() using anexplicit type specification. E.g.,fun<int>() callsfun, instantiated forint. This is of coursenot the same ascompiler argument deduction.
In general, when a function has multiple parameters of identical templatetype parameters, the actual types must be exactly the same. So, whereas
void binarg(double x, double y);
may be called using anint and adouble, with theint argumentsilently being converted to adouble, a similar function template cannotbe called using anint anddouble argument: the compiler won't byitself promoteint todouble deciding thatType should bedouble:
template <typename Type> void binarg(Type const &p1, Type const &p2) {} int main() { binarg(4, 4.5); // ?? won't compile: different actual types }What, then, are the transformations the compiler applies when deducingthe actual types of template type parameters? It performs but three typesof parameter type transformations and a fourth one to function templatenon-type parameters. If it cannot deduce the actual types using thesetransformations, the function template will not be considered. Thetransformations performed by the compiler are:
const modifier toa non-constant argument type;int tosize_t,int todouble, etc.).Anlvalue-to-rvalue transformation is applied when anrvalueis required, but anlvalueis provided. This happens when avariable is used as argument to a function specifying avalueparameter. For example,template<typename Type>Type negate(Type value){ return -value;}int main(){ int x = 5; x = negate(x); // lvalue (x) to rvalue (copies x)}
Anarray-to-pointer transformation is applied when the nameof an array is assigned to a pointer variable. This is frequently used withfunctions defining pointer parameters. Such functions frequently receivearrays as their arguments. The array's address is then assigned to thepointer-parameter and its type is used to deduce the corresponding templateparameter's type. For example:template<typename Type>Type sum(Type *tp, size_t n){ return accumulate(tp, tp + n, Type());}int main(){ int x[10]; sum(x, 10);}In this example, the location of the array
xis passed tosum,expecting a pointer to some type. Using the array-to-pointer transformation,x's address is considered a pointer value which is assigned totp,deducing thatTypeisintin the process.
This transformation is most frequently used with functiontemplates defining a parameter which is a pointer to a function. When callingsuch a function the name of a function may be specified as its argument. Theaddress of the function is then assigned to the pointer-parameter, deducingthe template type parameter in the process. This is called afunction-to-pointer transformation. For example:#include <cmath>template<typename Type>void call(Type (*fp)(Type), Type const &value){ (*fp)(value);}int main(){ call(sqrt, 2.0);}In this example, the address of thesqrtfunction is passed tocall, expecting a pointer to a function returning aTypeand expectingaTypefor its argument. Using the function-to-pointer transformation,sqrt's address is assigned tofp, deducing thatTypeisdoublein the process (note thatsqrtis theaddress of a function, not avariable that is a pointer to a function, hence the lvaluetransformation).The argument
2.0could not have been specified as2as there is noint sqrt(int)prototype. Furthermore, the function's first parameterspecifiesType (*fp)(Type), rather thanType (*fp)(Type const &)asmight have been expected from our previous discussion about how to specify thetypes of function template's parameters, preferring references over values.However,fp's argumentTypeis not a function template parameter, buta parameter of the functionfppoints to. Sincesqrthas prototypedouble sqrt(double), rather thandouble sqrt(double const &),call's parameterfpmust be specified asType(*fp)(Type). It's that strict.
const orvolatilequalifications topointers. This transformation is applied when thefunction template's type parameter explicitly specifiesconst (orvolatile) but the function's argument isn't aconst orvolatileentity. In that caseconst orvolatile is provided by the compiler.Subsequently the compiler deduces the template's type parameter. For example: template<typename Type> Type negate(Type const &value) { return -value; } int main() { int x = 5; x = negate(x); }Here we see the function template'sType const &value parameter: areference to aconst Type. However, the argument isn't aconst int,but anint that can be modified. Applying a qualification transformation,the compiler addsconst tox's type, and so it matchesint constx. This is then matched againstType const &value allowing the compilerto deduce thatType must beint.
In section22.11 it is shown how aclass template canbe derived from another class template.
As class template derivation remains to be covered, the followingdiscussion is necessarily somewhat premature. The reader may of course skipbriefly to section22.11 returning back to this sectionthereafter.
In this section it should be assumed, for the sake of argument, that aclass templateVector has somehow been derived from astd::vector.Furthermore, assume that the following function template has beenconstructed to sort a vector using some function objectobj:
template <typename Type, typename Object> void sortVector(std::vector<Type> vect, Object const &obj) { sort(vect.begin(), vect.end(), obj); }To sortstd::vector<string> objects case-insensitively, aclassCaseless could be constructed as follows:
class CaseLess { public: bool operator()(std::string const &before, std::string const &after) const { return strcasecmp(before.c_str(), after.c_str()) < 0; } };Now various vectors may be sorted usingsortVector():
int main() { std::vector<string> vs; std::vector<int> vi; sortVector(vs, CaseLess()); sortVector(vi, less<int>()); }Applying the transformationtransformation to a base class instantiated from a class template, thefunction templatesortVector may now also be used to sortVectorobjects. For example:
int main() { Vector<string> vs; // `Vector' instead of `std::vector' Vector<int> vi; sortVector(vs, CaseLess()); sortVector(vi, less<int>()); }In this example,Vectors were passed as argument tosortVector. Applying the transformation to a base class instantiated froma class template, the compiler considersVector to be astd::vectorenabling it to deduce the template's type parameter. Astd::string for theVector vs, anint forVector vi.
Type isint if the argument isint x, andthe function's parameter isType &value).int and adouble argument:template <typename Type>Type add(Type const &lhs, Type const &rhs){ return lhs + rhs;}When calling this function template, two identical types must be used(albeit that the three standard transformations are of course allowed). If thetemplate deduction mechanism does not come up with identical actual types foridentical template types, then the function template is not going to beinstantiated.
In such cases the compiler performs type contractions. Doubling identicalreference types results in a simple contraction: the type is deduced to be asingle reference type. Example: if the template parameter type is specified asaType && and the actual parameter is anint && thenType isdeduced to be anint, rather than anint &&.
This is fairly intuitive. But what happens if the actual type isint &?There is no such thing as anint & &¶m and so the compiler contractsthe double reference by removing the rvalue reference, keeping the lvaluereference. Here the following rules are applied:
1. A function template parameter defined as an lvalue reference to a template's type parameter (e.g.,Examples:Type &) receiving an lvalue reference argument results in a single lvalue reference.2. A function template parameter defined as an rvalue reference to a template's type parameter (e.g.,
Type &&) receiving any kind of reference argument uses the reference type of the argument.
Actual & argument thenType & becomes anActual & andType is inferred asActual;Actual & thenType && becomes anActual & andType is inferred asActual;Actual && thenType & also becomesActual & andType is inferred asActual;Actual && thenType && becomesActual && andType is inferred asActual;Let's look at a concrete example where contraction occurs. Consider thefollowing function template where a function parameter is defined as an rvaluereferences to some template type parameter:
template <typename Type> void function(Type &¶m) { callee(static_cast<Type &&>(param)); }In this situation, whenfunction is called with an (lvalue) argument oftypeTP & the template type parameterType is deduced to beTp&. Therefore,Type &¶m is instantiated asTp ¶m,TypebecomesTp and the rvalue reference is replaced by an lvalue reference.
Likewise, whencallee is called using thestatic_cast the samecontraction occurs, soType &¶m operates onTp ¶m. Therefore(using contraction) the static castalso uses typeTp ¶m. Ifparam happened to be of typeTp && then the static cast uses typeTp &¶m.
This characteristic allows us to pass a function argument to a nested functionwithout changing its type: lvalues remain lvalues, rvalues remainrvalues. This characteristic is therefore also known asperfect forwarding which is discussed in greater detail in section22.5.2. Perfect forwarding prevents the template author from having todefine multiple overloaded versions of a function template.
algorithmon my old laptop takes about four times the amount of time it takes to compilea plain header file likecmath. The header fileiostream is evenharder to process, requiring almost 15 times the amount of time it takes toprocesscmath. Clearly, processing templates is serious business for thecompiler. On the other hand this drawback shouldn't be taken too seriously.Compilers are continuously improving their template processing capacity andcomputers keep getting faster and faster. What was a nuisance a few years agois hardly noticeable today.When templates are declared, the compiler does not have to process thetemplate's definitions again and again; and no instantiations are createdon the basis of template declarations alone. Any actually requiredinstantiation must then be available elsewhere (of course, this holds true fordeclarations in general). Unlike the situation we encounter with ordinaryfunctions, which are usually stored in libraries, it is currently not possibleto store templates in libraries (although the compiler may constructprecompiled headerfiles). Consequently, using template declarationsputs a burden on the shoulders of the software engineer, who has to make surethat the required instantiations exist. Below a simple way to accomplish thatis introduced.
To create a function template declaration simply replace the function'sbody by a semicolon. Note that this is exactly identical to the way ordinaryfunction declarations are constructed. So, the previously defined functiontemplateadd can simply be declared as
template <typename Type> Type add(Type const &lhs, Type const &rhs);
We've already encounteredtemplate declarations. The header fileiosfwd may be included in sources not requiring instantiations of elementsfrom the classios and its derived classes. For example, to compile thedeclaration
std::string getCsvLine(std::istream &in, char const *delim);
it is not necessary to include thestring andistream headerfiles. Rather, a single
#include <iosfwd>
is sufficient. Processingiosfwd requires only a fraction of the timeit takes to process thestring andistream header files.
For this a variant of a template declaration is available, a so-calledexplicit instantiation declaration. An explicit instantiation declaration consists of the following elements:
template, omitting the templateparameter list.Using explicit instantiation declarations all instantiations of templatefunctions required by a program can be collected in one file. This file, whichshould be a normalsource file, should include the template definitionheader file and should subsequently specify the required explicitinstantiation declarations. Since it's a source file, it is not includedby other sources. So namespaceusing directives and declarations maysafely be used once the required headers have been included. Here is anexample showing the required instantiations for our earlieradd functiontemplate, instantiated fordouble,int, andstd::string types:
#include "add.h" #include <string> using namespace std; template int add<int>(int const &lhs, int const &rhs); template double add<double>(double const &lhs, double const &rhs); template string add<string>(string const &lhs, string const &rhs);
If we're sloppy and forget to mention an instantiation required by ourprogram then the repair is easily made by adding the missing instantiationdeclaration to the above list. After recompiling the file and relinking theprogram we're done.
So, when is a function template actually instantiated? There are twosituations where the compiler decides to instantiate templates:
add is called with a pair ofsize_t values);char (*addptr)(char const &, char const &) = add;
The compiler is not always able to deduce the template's type parametersunambiguously. When the compiler reports an ambiguity it must be solved by thesoftware engineer. Consider the following code:
#include <iostream> #include "add.h" size_t fun(int (*f)(int *p, size_t n)); double fun(double (*f)(double *p, size_t n)); int main() { std::cout << fun(add); } When this little program is compiled, the compiler reports an ambiguity itcannot resolve. It has two candidate functions as for each overloaded versionoffun anadd function can be instantiated:error: call of overloaded 'fun(<unknown type>)' is ambiguous note: candidates are: int fun(size_t (*)(int*, size_t)) note: double fun(double (*)(double*, size_t))
Such situations should of course be avoided. Function templates can onlybe instantiated if there's no ambiguity. Ambiguities arise when multiplefunctions emerge from the compiler's function selection mechanism (see section21.14). It is up to us to resolve the ambiguities. Theycould be resolved using a bluntstatic_cast (by which we select amongalternatives, all of them possible and available):
#include <iostream> #include "add.h" int fun(int (*f)(int const &lhs, int const &rhs)); double fun(double (*f)(double const &lhs, double const &rhs)); int main() { std::cout << fun( static_cast<int (*)(int const &, int const &)>(add) ); } But it's good practice to avoid type casts wherever possible. How to dothis is explained in the next section (21.7).source1.cc defines a functionfun, instantiatingadd forint-type arguments, includingadd's templatedefinition. It displaysadd's address usingunion PointerUnion: union PointerUnion { int (*fp)(int const &, int const &); void *vp; };Here is a program usingPointerUnion:
#include <iostream> #include "add.h" #include "pointerunion.h" void fun() { PointerUnion pu = { add }; std::cout << pu.vp << '\n'; }source2.cc defines the same function, but merely declares theproperadd template using a template declaration (not an instantiationdeclaration). Here issource2.cc: #include <iostream> #include "pointerunion.h" template<typename Type> Type add(Type const &, Type const &); void fun() { PointerUnion pu = { add }; std::cout << pu.vp << '\n'; }main.cc again includesadd's template definition,declares the functionfun and definesmain, definingaddforint-type arguments as well and displayingadd's functionaddress. It also calls the functionfun. Here ismain.cc: #include <iostream> #include "add.h" #include "pointerunion.h" void fun(); int main() { PointerUnion pu = { add }; fun(); std::cout << pu.vp << '\n'; }source1.o (1912 bytes usingg++ version 4.3.4 (sizes of objectmodules reported in this section may differ for different compilers and/orrun-time libraries)) andsource2.o (1740 bytes). Sincesource1.ocontains the instantiation ofadd, it is somewhat larger thansource2.o, containing only the template's declaration. Now we're ready tostart our little experiment.main.o andsource1.o, we obviously link together twoobject modules, each containing its own instantiation of the same templatefunction. The resulting program produces the following output:0x80486d8 0x80486d8
Furthermore, the size of the resulting program is 6352 bytes.
main.o andsource2.o, we now link together an objectmodule containing the instantiation of theadd template, and anotherobject module containing the mere declaration of the same templatefunction. So, the resulting program cannot but contain a single instantiationof the required function template. This program has exactly the same size, andproduces exactly the same output as the first program.fun) existed, expecting different types of arguments. Theambiguity resulted from the fact that both arguments could have been providedby an instantiation of a function template. The intuitive way to solve such anambiguity is to use astatic_cast. But casts should be avoided whereverpossible.With function templates static casts may indeed be avoided usingexplicit template type arguments. Explicit template type arguments canbe used to inform the compiler about the actual types it should use wheninstantiating a template. To use explicit type arguments the function's nameis followed by anactual template type argument list which may again befollowed by the function's argument list. The actual types mentioned in theactual template argument list are used by the compiler to `deduce' what typesto use when instantiating the template. Here is the example from the previoussection, now using explicit template type arguments:
#include <iostream> #include "add.h" int fun(int (*f)(int const &lhs, int const &rhs)); double fun(double (*f)(double const &lhs, double const &rhs)); int main() { std::cout << fun(add<int>) << '\n'; }Explicit template type arguments can be used in situations where thecompiler has no way to detect which types should actually be used. E.g., insection21.4 the function templateType fun() was defined. Toinstantiate this function for thedouble type, we can callfun<double>().
add template. That template was designed toreturn the sum of two entities. If we would want to compute the sum of threeentities, we could write: int main() { add(add(2, 3), 4); }This is an acceptable solution for the occasional situation. However, ifwe would have to add three entities regularly, anoverloaded version oftheadd function expecting three arguments might be a useful function tohave. There's a simple solution to this problem: function templates may beoverloaded.
To define an overloaded function template, merely put multiple definitionsof the template in its header file. For theadd function this would boildown to:
template <typename Type> Type add(Type const &lhs, Type const &rhs) { return lhs + rhs; } template <typename Type> Type add(Type const &lhs, Type const &mid, Type const &rhs) { return lhs + mid + rhs; }The overloaded function does not have to be defined in terms of simplevalues. Like all overloaded functions, a unique set of function parameters isenough to define an overloaded function template. For example, here's anoverloaded version that can be used to compute the sum of the elements of avector:
template <typename Type> Type add(std::vector<Type> const &vect) { return accumulate(vect.begin(), vect.end(), Type()); }When overloading function templates we do not have to restrict ourselvesto the function's parameter list. The template's type parameter list itselfmay also be overloaded. The last definition of theadd template allows us tospecify avector as its first argument, but nodeque ormap. Overloaded versions for those types of containers could of course beconstructed, but how far should we go? A better approach seems to be to lookfor common characteristics of these containers. If found we may be able todefine an overloaded function template based on these commoncharacteristics. One common characteristic of the mentioned containers is thatthey all supportbegin andend members, returning iterators. Usingthis, we could define a template type parameter representing containers thatmust support these members. But mentioning a plain `container type' doesn'ttell us for what type of data it was instantiated. So we need a secondtemplate type parameter representing the container's data type, thusoverloading the template's type parameter list. Here is the resultingoverloaded version of theadd template:
template <typename Container, typename Type> Type add(Container const &cont, Type const &init) { return std::accumulate(cont.begin(), cont.end(), init); }One may wonder whether theinit parameter could not be left out of theparameter list asinit often has a default initialization value. Theanswer is `yes', but there are complications. Itis possible to define theadd function as follows:
template <typename Type, typename Container> Type add(Container const &cont) { return std::accumulate(cont.begin(), cont.end(), Type()); }Note, however, that the template's type parameters were reordered, whichis necessary because the compiler won't be able to determineType in acall like:
int x = add(vectorOfInts);
After reordering the template type parameters, puttingType first, anexplicit template type argument can be provided for the first template typeparameter:
int x = add<int>(vectorOfInts);
In this example we provided avector<int> argument. One might wonderwhy we have to specifyint explicitly to allow the compiler to determinethe template type parameterType. In fact, we don't. A third kind oftemplate parameter exists, atemplate template parameter, allowing thecompiler to determineType directly from the actual containerargument. Template template parameters are discussed in section23.4.
using namespace std; int main() { vector<int> v; add(3, 4); // 1 (see text) add(v); // 2 add(v, 0); // 3 }int. It therefore instantiatesadd<int>, our very first definitionof theadd template.add requiring but one argument. Itfinds the overloaded function template expecting astd::vector, deducingthat the template's type parameter must beint. It instantiatesadd<int>(std::vector<int> const &)
add template's first definition can't be used. But itcan use thelast definition, expecting entities having different types. As astd::vector supportsbegin andend, the compiler is now able toinstantiate the function templateadd<std::vector<int>, int>(std::vector<int> const &, int const &)
add function template for two equal and twodifferent template type parameters we've exhausted the possibilities for usinganadd function template having two template type parameters.add thisintroduces an ambiguity as the compiler won't be able to choose which ofthe two overloaded versions defining two differently typed function parametersshould be used. For example when defining: #include "add.h" template <typename T1, typename T2> T1 add(T1 const &lhs, T2 const &rhs) { return lhs + rhs; } int main() { add(3, 4.5); }the compiler reports an ambiguity like the following:
error: call of overloaded `add(int, double)' is ambiguous error: candidates are: Type add(const Container&, const Type&) [with Container = int, Type = double] error: T1 add(const T1&, const T2&) [with T1 = int, T2 = double]
Now recall the overloaded function template accepting three arguments:
template <typename Type> Type add(Type const &lhs, Type const &mvalue, Type const &rhs) { return lhs + mvalue + rhs; }It may be considered as a disadvantage that only equally typed argumentsare accepted by this function (threeints, threedoubles, etc.). Toremedy this we define yet another overloaded function template, this timeaccepting arguments of any type. This function template can only be used ifoperator+ is defined between the function's actually used types, but apartfrom that there appears to be no problem. Here is the overloaded versionaccepting arguments of any type:
template <typename Type1, typename Type2, typename Type3> Type1 add(Type1 const &lhs, Type2 const &mid, Type3 const &rhs) { return lhs + mid + rhs; }Now that we've defined the above two overloaded function templatesexpecting three arguments let's calladd as follows:
add(1, 2, 3);
Should we expect an ambiguity here? After all, the compiler might selectthe former function, deducing thatType == int, but it might also selectthe latter function, deducing thatType1 == int, Type2 == int andType3== int. Remarkably, the compiler reports no ambiguity.
No ambiguity is reported because of the following. If overloaded templatefunctions are defined usingless andmore specialized template typeparameters (e.g., less specialized: all types different vs. more specialized: all types equal)then the compiler selects the more specialized function whenever possible.
As arule of thumb: overloaded function templates must allow a uniquecombination of template type arguments to be specified to prevent ambiguitieswhen selecting which overloaded function template to instantiate. Theordering of template type parameters in the function template's typeparameter list is not important. E.g., trying to instantiate one of thefollowing function templates results in an ambiguity:
template <typename T1, typename T2> void binarg(T1 const &first, T2 const &second) {} template <typename T1, typename T2> void binarg(T2 const &first, T1 const &second) {}This should not come as a surprise. After all, template type parametersare just formal names. Their names (T1,T2 orWhatever) have noconcrete meanings.
add accepting certain containers:template <typename Container, typename Type>Type add(Container const &container, Type const &init);
template int add<std::vector<int>, int> (std::vector<int> const &vect, int const &init);
std::vector<int> vi;int sum = add<std::vector<int>, int>(vi, 0);
add template, defining two identically typed parametersworks fine for all types supportingoperator+ and a copyconstructor. However, these assumptions are not always met. For example, withchar *s, usingoperator+ or a `copy constructor' does not makesense. The compiler tries to instantiate the function template, butcompilation fails asoperator+ is not defined for pointers.In such situations the compiler may be able to resolve the template typeparameters but it (or we ...) may then detect that the standard implementationis pointless or produces errors.
To solve this problem atemplate explicit specialization may bedefined. A template explicit specialization defines the function template forwhich a generic definition already exists using specific actual template typeparameters. As we saw in the previous section the compiler always prefersa more specialized function over a less specialized one. So the templateexplicit specialization is selected whenever possible.
A template explicit specialization offers a specialization for its templatetype parameter(s). The special type is consistently substituted forthe template type parameter in the function template's code. Forexample if the explicitly specialized type ischar const * then in thetemplate definition
template <typename Type> Type add(Type const &lhs, Type const &rhs) { return lhs + rhs; }Type must be replaced bychar const *, resulting in a functionhaving prototype
char const *add(char const *const &lhs, char const *const &rhs);
Now we try to use this function:
int main(int argc, char **argv) { add(argv[0], argv[1]); }However, the compiler ignores our specialization and tries to instantiatethe initial function template. This fails, leaving us wondering why it didn'tselect the explicit specialization....
To see what happened here we replay, step by step, the compiler's actions:
add is called withchar * arguments.Type equalschar *.char * template typeargument match achar const *const & template parameter? Hereopportunities for the allowable transformations from section21.4may arise. A qualification transformation seems to be the only viable one,allowing the compiler to bind a const-parameter to a non-const argument.Type the compiler can match an argument of someType or an argument of someType const to aType const &.Type itself is not modified, and soType is achar *.char const *.char const * is not achar * it rejects the explicitspecialization and uses the generic form, resulting in a compilation error.add function template should also be able to handlechar *template type arguments another explicit specialization forchar * may berequired, resulting in the prototypechar *add(char *const &lhs, char *const &rhs);
Instead of defining another explicit specialization anoverloadedfunction template could be designed expecting pointers. The following functiontemplate definition expects two pointers to constantType values andreturns a pointer to a non-constantType:
template <typename Type> Type *add(Type const *t1, Type const *t2) { std::cout << "Pointers\n"; return new Type; }What actual types may be bound to the above function parameters? In thiscase only aType const *, allowingchar const *'s to be passed asarguments. There's no opportunity for a qualification transformation here.The qualification transformation allows the compiler to add aconst to anon-const argument if the parameter itself (andnotType) isspecified in terms of aconst orconst &. Looking at, e.g.,t1 wesee that it's defined as aType const *. There's nothingconst herethat's referring to the parameter (in which case it would have beenTypeconst *const t1 orType const *const &t1). Consequently a qualificationtransformation cannot be applied here.
As the above overloaded function template only acceptschar const *arguments, it will not accept (without a reinterpret cast)char *arguments. Somain'sargv elements cannot be passed to our overloadedfunction template.
Type * arguments? It is possible, but at some point itshould become clear that our approach doesn't scale. Like ordinary functionsand classes, function templates should have one conceptually clearpurpose. Trying to add overloaded function templates to overloaded functiontemplates quickly turns the template into a kludge. Don't use this approach. Abetter approach is to construct the template so that it fits its originalpurpose, to make allowances for the occasional specific case and to describeits purpose clearly in its documentation.In some situations constructing template explicit specializations may ofcourse be defensible. Two specializations forconst and non-constpointers to characters might be appropriate for ouradd functiontemplate. Here's how they areconstructed:
template.error: template-id `add<char*>' for `char* add(char* const&, char* const&)' does not match any template declaration
Here are two explicit specializations for the function templateadd,expectingchar * andchar const * arguments:
template <> char *add<char *>(char *const &p1, char *const &p2) { std::string str(p1); str += p2; return strcpy(new char[str.length() + 1], str.c_str()); } template <> char const *add<char const *>(char const *const &p1, char const *const &p2) { static std::string str; str = p1; str += p2; return str.c_str(); }Template explicit specializations are normally included in the filecontaining the other function template's implementations.
Whendeclaring a template explicit specialization the pair ofangle brackets following thetemplate keyword are essential. Ifomitted, we would have constructed atemplate instantiation declaration. The compiler would silently process it, at the expense of a somewhatlonger compilation time.
When declaring a template explicit specialization (or when using aninstantiation declaration) the explicit specification of the template type parameters can be omitted ifthe compiler is able to deduce these types from the function's arguments. Asthis is the case with thechar (const) * specializations, they could alsobe declared as follows:
template <> char *add(char *const &p1, char *const &p2); template <> char const *add(char const *const &p1, char const *const &p2);
If in additiontemplate <> would be omitted the function declaration wouldno longer be a function template declaration but an ordinary functiondeclaration. This is not an error: function templates and ordinary(non-template) functions may mutually overload each other. Ordinary functionsare not as restrictive as function templates with respect to allowed typeconversions. This could be a reason to overload a template with an ordinaryfunction every once in a while.
A function template explicit specialization is not just another overloaded version ofthe function template. Whereas an overloaded version may define a completelydifferent set of template parameters, a specialization must use the same setof template parameters as its non-specialized variant. The compiler uses thespecialization in situations where the actual template arguments match thetypes defined by the specialization (following the rule that the mostspecialized set of parameters matching a set of arguments will beused). For different sets of parameters overloaded versions of functions (orfunction templates) must be used.
std::string conversionoperator (cf. section11.3).A conversion operator is guaranteed to be used as an rvalue. This means thatobjects of a class defining astring conversion operator can be assignedto, e.g.,string objects. But when trying to insert objects definingstring conversion operators into streams then the compiler complains thatwe're attempting to insert an inappropriate type into anostream.
On the other hand, when this class defines anint conversion operatorinsertion is performed flawlessly.
The reason for this distinction is thatoperator<< is defined as a plain(free) function when inserting a basic type (likeint) but it is definedas a function template when inserting astring. Hence, when trying toinsert an object of our class defining astring conversion operator thecompiler visits all overloaded versions of insertion operators inserting intoostream objects.
Since no basic type conversion is available the basic type insertion operatorscan't be used. Since the available conversions for template arguments do notallow the compiler to look for conversion operators our class defining thestring conversion operator cannot be inserted into anostream.
If it should be possible to insert objects of such a class intoostreamobjects the class must define its own overloaded insertion operator (inaddition to thestring conversion operator that was required to use theclass's objects as rvalue instring assignments).
static_assert(constant expression, error message)
utility is available to allow assertions to be made from inside templatedefinitions. Here are two examples of its use:
static_assert(BUFSIZE1 == BUFSIZE2, "BUFSIZE1 and BUFSIZE2 must be equal"); template <typename Type1, typename Type2> void rawswap(Type1 &type1, Type2 &type2) { static_assert(sizeof(Type1) == sizeof(Type2), "rawswap: Type1 and Type2 must have equal sizes"); // ... }The first example shows how to avoid yet another preprocessor directive (in this case the#error directive).
The second example shows howstatic_assert can be used to ensure thata template operates under the right condition(s).
The string defined instatic_assert's second argument is displayed andcompilation stops if the condition specified instatic_assert's firstargument isfalse.
Like the#error preprocessor directivestatic_assert is acompile-time matter that doesn't have any effect on therun-timeefficiency of the code in which it is used.
<climits> defines constants for various types, e.g.,INT_MAX defines the maximum value that can be stored in anint.The disadvantage of the limits defined inclimits is that they are fixedlimits. Let's assume you write a function template that receives an argumentof a certain type. E.g,
template<typename Type> Type operation(Type &&type);
Assume this function should return the largest negative value forTypeiftype is a negative value and the largest positive value iftype isa positive value. However, 0 should be returned if the type is not an integralvalue.
How to proceed?
Since the constants inclimits can only be used if the type to use isalready known, the only approach seems to be to create function templatespecializations for the various integral types, like:
template<> int operation<int>(int &&type) { return type < 0 ? INT_MIN : INT_MAX; }The facilities provided bynumeric_limits provide an alternative. To usethese facilities the header file<limits> header file must be included.
The class templatenumeric_limits offers various members answering allkinds of questions that could be asked of numeric types. Before introducingthese members, let's have a look at how we could implement theoperationfunction template as just one single function template:
template<typename Type> Type operation(Type &&type) { return not numeric_limits<Type>::is_integer ? 0 : type < 0 ? numeric_limits<Type>::min() : numeric_limits<Type>::max(); }Nowoperation can be used for all the language's primitive types.
Here is an overview of the facilities offered bynumeric_limits. Notethat the member functions defined bynumeric_limits returnconstexprvalues. A member `member' defined bynumeric_limits for typeTypecan be used as follows:
numeric_limits<Type>::member // data members numeric_limits<Type>::member() // member functions
Type denorm_min():Type: its minimum positive denormalized value; otherwise it returnsnumeric_limits<Type>::min().int digits:Type values, or (floating point types) the number of digits in the mantissa are returned.int digits10:Type value without changing it.Type constexpr epsilon():Type between the smallest value exceeding 1 and 1 itself.float_denorm_style has_denorm:has_denorm member returns information about denormalized values for typeType:denorm_absent:Type does not allow denormalized values;denorm_indeterminate:Type may or may not use denormalized values; the compiler cannot determine this at compile-time;denorm_present:Type uses denormalized values;bool has_denorm_loss:true if a loss of accuracy was detected as a result of using denormalization (rather than being an inexact result).bool has_infinity:true ifType has a representation for positive infinity.bool has_quiet_NaN:true ifType has a representation for a non-signaling`Not-a-Number' value.bool has_signaling_NaN:true ifType has a representation for a signaling `Not-a-Number' value.Type constexpr infinity():Type: its positive infinity value.bool is_bounded:true ifType contains a finite set of values.bool is_exact:true ifType uses an exact representation.bool is_iec559:true ifType uses theIEC-559 (IEEE-754) standard. Such types always returntrue forhas_infinity, has_quiet_NaN andhas_signaling_NaN, whileinfinity(), quiet_NaN() andsignaling_NaN() return non-zero values.bool is_integer:true ifType is an integral type.bool is_modulo:true ifType is a `modulo' type. Values of modulo types can always be added, but the addition may `wrap around' producing a smallerType result than either of the addition's two operands.bool is_signed:true ifType is signed.bool is_specialized:true for specializations ofType.Type constexpr lowest():Type's lowest finite value representable byType: no other finite value smaller than the value returned bylowest exists. This value equals the value returned bymin except for floating-point types.T constexpr max():Type's maximum value.T constexpr min():Type's minimum value. For denormalized floating point types the minimum positive normalized value.int max_exponent:Type producing a validType value.int max_exponent10:Type value.int min_exponent:Type producing a validType value.int min_exponent10:Type value.Type constexpr quiet_NaN():Type: its a non-signaling `Not-a-Number' value.int radix:Type is an integral type: base of the representation; ifType is a floating point type: the base of the exponent of the representation.Type constexpr round_error():Type.float_round_style round_syle:Type. It has one of the followingenumfloat_round_style values:round_toward_zero: values are rounded towards zero;round_to_nearest: values are rounded to the nearest representable value;round_toward_infinity:values are rounded towards infinity;round_toward_neg_infinity: if it rounds towards negative infinity;round_indeterminate: if the rounding style is indeterminable at compile-time.Type constexpr signaling_NaN():Type: its a signaling `Not-a-Number' value.bool tinyness_before:true ifType allows tinyness to be detected before rounding.bool traps:true ifType implements trapping.To solve this problempolymorphous (function object) wrappers can be used. Polymorphouswrappers refer to function pointers, member functions or function objects, aslong as their parameters match in type and number.
Before using polymorphic function wrappers the<functional> header filemust be included.
Polymorphic function wrappers are made available through thestd::function class template. Its template argumentis the prototype of the function to create a wrapper for. Here is an exampleof the definition of a polymorphic function wrapper that can be used to pointto a function expecting twoint values and returning anint:
std::function<int (int, int)> ptr2fun;
Here, the template's parameter isint (int, int), indicating afunction expecting twoint arguments, and returning andint. Otherprototypes return other, matching, function wrappers.
Such a function wrapper can now be used to point to any function the wrapperwas created for. E.g., `plus<int> add' creates a functor defining anint operator()(int, int) function call member. As this qualifies as afunction having prototypeint (int, int), ourptr2fun may point toadd:
ptr2fun = add;
Ifptr2fun does not yet point to a function (e.g., it is merelydefined) and an attempt is made to call a function through it a`std::bad_function_call' exception is thrown. Also, a polymorphic function wrapper that hasn't beenassigned to a function's address represents the valuefalse in logicalexpressions (as if it had been a pointer having value zero):
std::function<int(int)> ptr2int; if (not ptr2int) cout << "ptr2int is not yet pointing to a function\n";
Polymorphous function wrappers can also be used to refer to functions,functors or other polymorphous function wrappers having prototypes for whichstandard conversions exist for either parameters or return values. E.g.,
bool predicate(long long value); void demo() { std::function<int(int)> ptr2int; ptr2int = predicate; // OK, convertible param. and return type struct Local { short operator()(char ch); }; Local object; std::function<short(char)> ptr2char(object); ptr2int = object; // OK, object is a functor whose function // operator has a convertible param. and // return type. ptr2int = ptr2char; // OK, now using a polym. funct. wrapper }add function template: template <typename Container, typename Type> Type add(Container const &container, Type init) { return std::accumulate(container.begin(), container.end(), init); }Herestd::accumulate is called usingcontainer'sbegin andend members.
The callscontainer.begin() andcontainer.end() are said todepend on template type parameters. The compiler, not having seencontainer's interface, cannot check whethercontainer actuallyhas membersbegin andend returning input iterators.
On the other hand,std::accumulate itself is independent of anytemplate type parameter. Itsarguments depend on template parameters, butthe function call itself isn't. Statements in a template's body that areindependent of template type parameters are saidnot to depend on templatetype parameters.
When the compiler encounters a template definition, it verifies thesyntactic correctness of all statements not depending on templateparameters. I.e., it must have seen all class definitions, all typedefinitions, all function declarations etc. that are used in those statements.If the compiler hasn't seen the required definitions and declarations then itwill reject the template's definition. Therefore, when submitting the abovetemplate to the compiler thenumeric header file must first have beenincluded as this header file declaresstd::accumulate.
With statements depending on template parameters the compiler cannotperform those extensive syntactic checks. It has no way to verify theexistence of a memberbegin for the as yet unspecified typeContainer. In these cases the compiler performs superficial checks,assuming that the required members, operators and types eventually becomeavailable.
The location in the program's source where the template is instantiated is calleditspoint of instantiation. At the point of instantiation the compilerdeduces the actual types of the template's parameters. At that point it checksthe syntactic correctness of the template's statements that depend on templatetype parameters. This implies that the compiler must have seen the requireddeclarationsonly at the point of instantiation. As arule of thumb, you should make sure that all required declarations (usually: headerfiles) have been read by the compiler at every point of instantiation of thetemplate. For the template's definition itself a more relaxed requirement canbe formulated. When the definition is read only the declarations required forstatementsnot depending on the template's type parameters must have beenprovided.
On the other hand: at the point of instantiation the compiler mustalso have access to the template's definition. Therefore, templatedefinitions are commonly provided in header files: if a template must beinstantiated in some source file then the template definition, provided in aheader file (e.g.,template.h) is included by the source file, so thecompiler can verify whether the template can be instantiated for the actuallyused types (the same holds true for class templates, covered in the nextchapter). Here is a short example:
add is defined inadd.h: template <typename Type> Type add(Type const &fst, Type const &snd) { return fst + snd; }fun.h includesadd.h: #include <iostream> #include "add.h" vouid fun(size_t lhs, size_t rhs) { std::cout << add(lhs, rhs) << '\n'; }Function template definitions can be defined as inline functionsby prefixing their definitions with the keywordinline. E.g,
template <typename Type> inline Type add(Type const &fst, Type const &snd) { return fst + snd; } As always, inline should only be used for short, one-liner functiondefinitions.Assume we ask the compiler to compile the followingmain function:
int main() { process(3, 3); }Furthermore assume that the compiler has encountered the followingfunction declarations when it's about to compilemain:
template <typename T> void process(T &t1, int i); // 1 template <typename T1, typename T2> void process(T1 const &t1, T2 const &t2); // 2 template <typename T> void process(T const &t, double d); // 3 template <typename T> void process(T const &t, int i); // 4 template <> void process<int, int>(int i1, int i2); // 5 void process(int i1, int i2); // 6 void process(int i, double d); // 7 void process(double d, int i); // 8 void process(double d1, double d2); // 9 void process(std::string s, int i) // 10 int add(int, int); // 11
The compiler, having readmain's statement, must now decide whichfunction must actually be called. It proceeds as follows:
This implies that at least the number of arguments must match the number ofparameters of the viable functions. Function 10's first argument is astring. As astring cannot be initialized by anint value noappropriate conversion exists and function 10 is removed from the list ofcandidate functions.double parameters can be retained. Standardconversionsdo exists forints todoubles, so all functions havingordinarydouble parameters can be retained. Therefore, the set of viablefunctions consists of functions 1 through 9.
It may use any of the three standard template parameter transformationprocedures (cf. section21.4) when trying to match actual types totemplate type parameters. In this process it concludes that no type can bedetermined for theT in function 1'sT &t1 parameter as the argument3 is a constantint value. Thus function 1 is removed from the list ofviable functions. The compiler is now confronted with the following set ofpotentially instantiated function templates and ordinary functions:
void process(T1 [= int] const &t1, T2 [= int] const &t2); // 2 void process(T [= int] const &t, double d); // 3 void process(T [= int] const &t, int i); // 4 void process<int, int>(int i1, int i2); // 5 void process(int i1, int i2); // 6 void process(int i, double d); // 7 void process(double d, int i); // 8 void process(double d1, double d2); // 9
The compiler associates adirect match count value to each of theviable functions. The direct match count counts the number of arguments thatcan be matched to function parameters without an (automatic) typeconversion. E.g., for function 2 this count equals 2, for function 7 it is 1and for function 9 it is 0. The functions are now (decrementally) sorted bytheir direct match count values:
match count void process(T1 [= int] const &t1, T2 [= int] const &t2); 2 // 2 void process(T [= int] const &t, int i); 2 // 4 void process<int, int>(int i1, int i2); 2 // 5 void process(int i1, int i2); 2 // 6 void process(T [= int] const &t, double d); 1 // 3 void process(int i, double d); 1 // 7 void process(double d, int i); 1 // 8 void process(double d1, double d2); 0 // 9
If there is no draw for the top value the corresponding function isselected and the function selection process is completed.
When multiple functions appear at the top the compiler verifies that noambiguity has been encountered. An ambiguity is encountered if thesequences of parameters for which type conversions were (not) requireddiffer. As an example consider functions 3 and 8. Using D for `direct match'and C for `conversion' the arguments match function 3 as D,C and function 8 asC,D. Assuming that 2, 4, 5 and 6 were not available, then the compiler wouldhave reported an ambiguity as the sequences of argument/parameter matchingprocedures differ for functions 3 and 8. The same difference is encounteredcomparing functions 7 and 8, but no such difference is encountered comparingfunctions 3 and 7.
At this point there is a draw for the top value and the compilerproceeds with the subset of associated functions (functions 2, 4, 5 and6). With each of these functions an `ordinary parameter count' is associatedcounting the number of non-template parameters of the functions. The functionsare decrementally sorted by this count, resulting in:
ordin. param. count void process(int i1, int i2); 2 // 6 void process(T [= int] const &t, int i); 1 // 4 void process(T1 [= int] const &t1, T2 [= int] const &t2); 0 // 2 void process<int, int>(int i1, int i2); 0 // 5
Now there is no draw for the top value. The corresponding function(process(int, int), function 6) is selected and the function selectionprocess is completed. Function 6 is used inmain's function callstatement.
Had function 6 not been defined, function 4 would have been used. Assumingthat neither function 4 nor function 6 had been defined, the selectionprocess would continue with functions 2 and 5:
ordin. param. count void process(T1 [= int] const &t1, T2 [= int] const &t2); 0 // 2 void process<int, int>(int i1, int i2); 0 // 5
In this situation a draw is encountered once again and the selectionprocess continues. A `type of function' value is associated with each of thefunctions having the highest ordinary parameter count and these functions aredecrementally sorted by their type of function values. Value 2 is associatedto ordinary functions, value 1 to template explicit specializations and value0 to plain function templates.
If there is no draw for the top value the corresponding function isselected and the function selection process is completed. If there is a drawthe compiler reports an ambiguity and cannot determine which function tocall. Assuming only functions 2 and 5 existed then this selection stepwould have resulted in the following ordering:
function type void process<int, int>(int i1, int i2); 1 // 5 void process(T1 [= int] const &t1, T2 [= int] const &t2); 0 // 2
Function 5, the template explicit specialization, would have beenselected.

struct Int { using type = int; };Although at this point it may seem strange to embed a using declaration in astruct, in chapter23 we will encounter situations where thisis actually very useful. It allows us to define a variable of a type that isrequired by the template. E.g., (ignore the use oftypename in thefollowing function parameter list, but see section22.2.1 fordetails):
template <typename Type> void func(typename Type::type value) { }When callingfunc(10)Int has to be specified explicitly sincethere may be many structs that definetype: the compiler needs someassistance. The correct call isfunc<Int>(10). Now that it's clear thatInt is meant, and the compiler correctly deduces thatvalue is anint.
But templates may be overloaded and our next definition is:
template <typename Type> void func(Type value) {}Now, to call this function we specifyfunc<int>(10) andagain this flawlessly compiles.
But as we've seen in the previous section when the compiler determineswhich template to instantiate it creates a list of viable functions andselects the function to instantiate by matching the parameter types of viablefunctions with the provided actual argument types. To do so it has todetermine the types of the parameters and herein lies a problem.
When evaluatingType = int the compiler encounters the prototypesfunc(int::type) (first template definition) andfunc(int) (secondtemplate definition). But there is noint::type, and so in a way thisgenerates an error. The error results from matching the provided template typeargument with the types used in the various template definitions.
A type-problem caused by substituting a type in a template definition is,however,not considered an error, but merely an indication that thatparticular type cannot be used in that particular template. The template istherefore removed from the list of candidate functions.
This principle is known assubstitution failureis not an error(SFINAE) and it is often used by the compiler to select not only asimple overloaded function (as shown here) but also to choose among availabletemplate specializations (see also sections23.6.1 and23.9.3).
if (cond) selection statement theifconstexpr (cond) syntax is supported by the language. Although it can be usedin all situations where a standardif selection statement are used, itsspecific use is encountered inside function templates:if constexpr allowsthe compiler to (conditionally) instantiate elements of a template function,depending on the compile-time evaluation of theif constexpr's (cond)clause.Here is an example:
1: void positive(); 2: void negative(); 3: 4: template <int value> 5: void fun() 6: { 7: if constexpr (value > 0) 8: positive(); 9: else if constexpr (value < 0) 10: negative(); 11: } 12: 13: int main() 14: { 15: fun<4>(); 16: }if constexpr statements start. Sincevalue is a template non-type parameter its value is compile-time available, and so are the values of the condition sections.fun<4>() is called: the condition in line 7 is thereforetrue, and the condition in line 9 isfalse.fun<4>() this way: void fun<4>() { positive(); }if constexpr statements themselves do not result inexecutable code: it is used by the compiler toselect which part (orparts) it should instantiate. In this case onlypositive, which must beavailable before the program's linking phase can properly complete.Not every template declaration may be converted into a template definition. Ifa definition may be provided it is explicitly mentioned.
template <typename Type1, typename Type2>void function(Type1 const &t1, Type2 const &t2);
templatevoid function<int, double>(int const &t1, double const &t2);
void (*fp)(double, double) = function<double, double>;void (*fp)(int, int) = function<int, int>;
template <>void function<char *, char *>(char *const &t1, char *const &t2);
friend void function<Type1, Type2>(parameters);
A variable template starts with a familiartemplate header, followed bythe definition of the variable itself. The template header specifies a type,for which a default type may also be specified. E.g.,
template<typename T = long double> constexpr T pi = T(3.1415926535897932385);
To use this variable a type must be specified, and as long as the initializedvalue can be converted to the specified type the conversion is silently performed by the compiler:
cout << pi<> << ' ' << pi<int>;
At the second insertion thelong double initialization value isconverted toint, and 3 is displayed.
Specializations are also supported. E.g., to show the text `pi' aspecialization for achar const * type can be defined:
template<> constexpr char const *pi<char const *> = "pi";
With this specialization we can docout << pi<char const *> to showpi.