Movatterモバイル変換


[0]ホーム

URL:




Chapter 21: Function and Variable Templates

C++ supports syntactic constructs allowing programmers to define and usecompletely general (or abstract) functions or classes, based on generic typesand/or (possibly inferred) constant values. In the chapters on abstractcontainers (chapter12) and theSTL (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.

21.1: Defining function templates

Afunction template's definition is very similar to the definition ofa normal function. A function template has a function head, a function body, areturn type, possibly overloaded definitions, etc.. However, different fromordinary functions, function templates always use one or moreformal types: types for which almost any existing(class or primitive) type could be used. Let's have a look at a simpleexample. The following functionadd 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: Normal scope rules and identifier visibility rules apply to templates.Within the template definition's scope formal type names overrule identicallynamed identifiers of broader scopes.

21.1.1: Considerations regarding template parameters

We've managed to design our first function template:
    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?

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:

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.

21.1.2: Auto and decltype

In section3.3.7 theauto 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 string

In 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];        }    };

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 &.

21.1.2.1: declval

The keyworddecltype 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';    }

21.1.3: Late-specified return type

TraditionalC++ requires function templates to specify their return typeor to specify the return type as a template type parameter. Consider thefollowing function:
    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    }

21.2: Passing arguments by reference (reference wrappers)

Before using the reference wrappers discussed in this section the<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    */

21.3: Using local and unnamed types as template arguments

Usually, types have names. But ananonymous type may also be defined:
    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 char

The 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'    }

21.4: Template parameter deduction

In this section we concentrate on the process by which the compilerdeduces the actual types of the template type parameters. These types arededuced when a function template is called using a process calledtemplate parameter deduction. As we've already seen, the compiler isable to substitute a wide range of actual types for a single formal templatetype parameter. Even so, not every thinkable conversion is possible. Inparticular when a function has multiple parameters of the same template typeparameter, the compiler is very restrictive when determining what argumenttypes are actually accepted.

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:

The purpose of the various template parameter type deductiontransformations isnot to match function arguments to function parameters,but rather, having matched arguments to parameters, to determine theactualtypes of the various template type parameters.

21.4.1: Lvalue transformations

There are three types oflvalue transformations:

21.4.2: Qualification transformations

Aqualification transformation addsconst 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.

21.4.3: Transformation to a base class

Although theconstruction of class templates is the topic of chapter22, we've already extensivelyused class templates before. Forexample, abstract containers (cf. chapter12) are defined asclass templates. Class templates can, like ordinary classes, participate in the construction of class hierarchies.

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.

21.4.4: The template parameter deduction algorithm

The compiler uses the following algorithm to deduce the actual types of its template type parameters:

21.4.5: Template type contractions

With function templates the combination of the types of template arguments andtemplate parameters shows some interesting contractions. What happens, forexample if a template type parameter is specified as an rvalue reference butan lvalue reference argument type is provided?

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 & &&param 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.,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.

Examples:

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 &&param)    {        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 &&param is instantiated asTp &param,TypebecomesTp and the rvalue reference is replaced by an lvalue reference.

Likewise, whencallee is called using thestatic_cast the samecontraction occurs, soType &&param operates onTp &param. Therefore(using contraction) the static castalso uses typeTp &param. Ifparam happened to be of typeTp && then the static cast uses typeTp &&param.

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.

21.5: Declaring function templates

Up to now, we've only defined function templates. There are various consequences of including functiontemplate definitions in multiple source files, none of them serious, but worthknowing. So in some contexts templatedefinitions may not be required. Insteadthe software engineer may opt todeclare a template rather than toinclude the template's definition time and again in various source files.

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.

21.5.1: Instantiation declarations

If declaring function templates speeds up the compilation and the linkingphases of a program, how can we make sure that the required instantiations ofthe function templates are available when the program is eventually linkedtogether?

For this a variant of a template declaration is available, a so-calledexplicit instantiation declaration. An explicit instantiation declaration consists of the following elements:

Although this is a declaration, it is understood by the compiler as arequest to instantiate that particular variant of the function template.

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.

21.6: Instantiating function templates

Different from an ordinary function that results in code once the compilerreads its definition a template is not instantiated when its definition isread. A template is merely arecipe telling the compiler how to createparticular code once it's time to do so. It's indeed very much like a recipein a cooking book. You reading how to bake a cake doesn't mean you haveactually baked that cake by the time you've read the recipe.

So, when is a function template actually instantiated? There are twosituations where the compiler decides to instantiate templates:

The location of statements causing the compiler to instantiate a templateis called the template'spoint of instantiation. The point of instantiation has seriousimplications for the function template's code. These implications arediscussed in section21.13.

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).

21.6.1: Instantiations: no `code bloat'

As mentioned in section21.5, the linker removes identicalinstantiations of a template from the final program, leaving only oneinstantiation for each unique set of actual template type parameters. Toillustrate the linker's behavior we do as follows: From our little experiment we conclude that the linker indeed removesidentical template instantiations from a final program. Furthermore weconclude that using mere template declarations does not result in templateinstantiations.

21.7: Using explicit template types

In the previous section we saw that the compiler may encounter ambiguitieswhen attempting to instantiate a template. In an example overloaded versionsof a function (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>().

21.8: Overloading function templates

Let's once again look at ouradd 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.

21.8.1: An example using overloaded function templates

With all these overloaded versions in place, we may now start the compilerto compile the following function:
    using namespace std;    int main()    {        vector<int> v;        add(3, 4);          // 1 (see text)        add(v);             // 2        add(v, 0);          // 3    }

Having defined theadd function template for two equal and twodifferent template type parameters we've exhausted the possibilities for usinganadd function template having two template type parameters.

21.8.2: Ambiguities when overloading function templates

Although itis possible to define another function templateadd 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.

21.8.3: Declaring overloaded function templates

Like any function, overloaded functions may be declared, either usingplain declarations or instantiation declarations. Explicit template argumenttypes may also be used. Example:

21.9: Specializing templates for deviating types

The initialadd 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:

If ouradd function template should also be able to handlechar *template type arguments another explicit specialization forchar * may berequired, resulting in the prototype
    char *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.

21.9.1: Avoiding too many specializations

So do we have to define yet another overloaded function template, thistime expectingType * 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:

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.

21.9.2: Declaring specializations

Template explicit specializations can be declared in the usual way. I.e.,by replacing its body with a semicolon.

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.

21.9.3: Complications when using the insertion operator

Now that we've covered explicit specializations and overloading let'sconsider what happens when a class defines astd::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).

21.10: Static assertions

The
    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.

21.11: Numeric limits

The header file<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

21.12: Polymorphous wrappers for function objects

InC++ pointers to (member) functions have fairly strict rvalues. They canonly point to functions matching their types. This becomes a problem whendefining templates where the type of a function pointer may depend on thetemplate's parameters.

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    }

21.13: Compiling template definitions and instantiations

Consider this definition of theadd 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:

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.

21.14: The function selection mechanism

When the compiler encounters a function call, it must decide which function tocall when overloaded functions are available. Earlier we've encounteredprinciples like `the most specific function is selected'. This is afairly intuitive description of the compiler'sfunction selection mechanism.In this section we'll have a closer look at this mechanism.

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:

At this point the compiler tries to determine the types of the templatetype parameters. This step is outlined in the following subsection.

21.14.1: Determining the template type parameters

Having determined the set of candidate functions and from that set the set ofviable functions the compiler must now determine the actual types of thetemplate type parameters.

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.

Figure 28: The function template selection mechanism

Here is a summary of the function template selection mechanism (cf. figureFigure28):

21.15: SFINAE: Substitution Failure Is Not An Error

Consider the following struct definition:
    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).

21.16: Conditional function definitions using `if constexpr'

In addition to the commonif (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: }

Note that theif 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.

21.17: Summary of the template declaration syntax

In this section the basic syntactic constructions for declaring templatesare summarized. Whendefining templates, the terminatingsemicolon should be replaced by a function body.

Not every template declaration may be converted into a template definition. Ifa definition may be provided it is explicitly mentioned.

21.18: Variables as templates (template variables)

In addition to function templates and class templates (cf. chapter22)C++ supportsvariable templates. Variable templatesmight come in handy when defining (function or class) templates definingvariables of types specified by template type 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.




[8]ページ先頭

©2009-2025 Movatter.jp