Movatterモバイル変換


[0]ホーム

URL:




Chapter 23: Advanced Template Use

The main purpose of templates is to provide a generic definition of classesand functions that may then be tailored to specific types.

But templates allow us to do more than that. If not for compilerimplementation limitations, templates could be used to program, atcompile-time, just about anything we use computers for. This remarkable feat,offered by no other current-day computer language, stems from the fact thattemplates allow us to do three things at compile-time:

Of course, asking the compiler to compute, e.g., prime numbers, is onething. But it's a completely different thing to do so in an award winningway. Don't expect speed records to be broken when the compiler performscomplex calculations for us. But that's all beside the point. In the end wecan ask the compiler to compute virtually anything usingC++'stemplate language, including prime numbers....

In this chapter these remarkable features of templates arediscussed. Following a short overview of subtleties related to templates themain characteristics oftemplate meta programming are introduced.

In addition to template type and template non-type parameters there is a thirdkind of template parameter, thetemplate template parameter. This kind of template parameter isintroduced shortly, laying the groundwork for the discussion oftraitclasses andpolicy classes.

This chapter ends with the discussion of several additional and interestingapplications of templates: adapting compiler error messages, conversions toclass types and an elaborate example discussing compile-time list processing.

Much of the inspiration for this chapter came from two highly recommendedbooks: Andrei Alexandrescu's 2001 bookModern C++ design (Addison-Wesley) and NicolaiJosutis and David Vandevoorde's 2003 bookTemplates (Addison-Wesley).

23.1: Subtleties

In section22.2.1 a special application of the keywordtypenamewas discussed. There we learned that it is not only used to define a name fora (complex) type, but also to distinguish types defined by class templatesfrom members defined by class templates. In this section another applicationis introduced In section23.1.1 we cover the problem of how to refer tobase class templates from derived class templates.

In addition to the special applications oftypename section23.1.2 introduces some new syntax that is related to the extended use ofthe keywordtypename:::template, .template and->template areused to inform the compiler that a name used inside a template is itself aclass template.

23.1.1: Type resolution for base class members

Below we see two class templates.Base andDerived,Base beingDerived's base class:
    #include <iostream>    template <typename T>    class Base    {        public:            void member();    };    template <typename T>    void Base<T>::member()    {        std::cout << "This is Base<T>::member()\n";    }    template <typename T>    class Derived: public Base<T>    {        public:            Derived();    };    template <typename T>    Derived<T>::Derived()    {        member();    }
This example won't compile, and the compiler tells us something like:
    error: there are no arguments to 'member' that depend on a template           parameter, so a declaration of 'member' must be available

This error causes some confusion as ordinary (non-template) base classesreadily make their public and protected members available to classes that arederived from them. This is no different for class templates, but only if thecompiler can figure out what we mean. In the above example the compilercan't as it doesn't know for what typeT the member functionmember must be initialized when called fromDerived<T>::Derived.

To appreciate why this is true, consider the situation where we havedefined a specialization:

    template <>    Base<int>::member()    {        std::cout << "This is the int-specialization\n";    }

Since the compiler, whenDerived<SomeType>::Derived is called, doesnot know whether a specialization ofmember is in effect, it can'tdecide (when compilingDerived<T>::Derived) for what type to instantiatemember. It can't decide this when compilingDerived<T>::Derived asmember's call inDerived::Derived doesn't require a template typeparameter.

In cases like these, where no template type parameter is available todetermine which type to use, the compiler must be told that it should postponeits decision about the template type parameter to use (and therefore about theparticular (here:member) function to call)until instantiation time.

This may be implemented in two ways: either by usingthis or byexplicitly mentioning the base class, instantiated for the derived class'stemplate type(s). Whenthis is used the compiler is informed that we'rereferring to the typeT for which the template was instantiated. Anyconfusion about which member function to use (the derived class or base classmember) is resolved in favor of the derived class member. Alternatively, thebase or derived class can explicitly be mentioned (usingBase<T> orDerived<T>) as shown in the next example. Note that with theinttemplate type theint specialization is used.

    #include <iostream>    template <typename T>    class Base    {        public:            virtual void member();    };    template <typename T>    void Base<T>::member()    {        std::cout << "This is Base<T>::member()\n";    }    template <>    void Base<int>::member()    {        std::cout << "This is the int-specialization\n";    }    template <typename T>    class Derived: public Base<T>    {        public:            Derived();            void member() override;    };    template <typename T>    void Derived<T>::member()    {        std::cout << "This is Derived<T>::member()\n";    }    template <typename T>    Derived<T>::Derived()    {        this->member();         // Using `this' implies using the                                // type for which T was instantiated        Derived<T>::member();   // Same: calls the Derived member        Base<T>::member();      // Same: calls the Base member        std::cout << "Derived<T>::Derived() completed\n";    }    int main()    {        Derived<double> d;        Derived<int> i;    }    /*        Generated output:    This is Derived<T>::member()    This is Derived<T>::member()    This is Base<T>::member()    Derived<T>::Derived() completed    This is Derived<T>::member()    This is Derived<T>::member()    This is the int-specialization    Derived<T>::Derived() completed    */
The above example also illustrates the use of virtual member templates(although virtual member templates aren't often used). In the exampleBasedeclares avirtual void member andDerived defines its overridingfunctionmember. In that casethis->member() inDerived::Derivedcalls, due tomember's virtual nature,Derived::member. The statementBase<T>::member(), however, always callsBase'smember functionand can be used to bypass dynamic polymorphism.

23.1.2: ::template, .template and ->template

In general, the compiler is able to determine the true nature of a name. Asdiscussed, this is not always the case and sometimes we have to advise thecompiler. Thetypename keyword is often used for that purpose.

While parsing a source the compiler receives a series oftokens,representing meaningful units of text encountered in the program's source. Atoken could represent, e.g., an identifier or a number. Other tokens representoperators, like=, + or<. It is precisely the last token that maycause problems as it may have very different meanings. The correct meaningcannot always be determined from the context in which the compiler encounters<. In some situations the compilerdoes know that< does notrepresent theless than operator, as when a template parameter listfollows the keywordtemplate, e.g.,

    template <typename T, int N>

Clearly, in this case< does not represent a `less than' operator.

The special meaning of< when it is preceded bytemplate forms thebasis for the syntactic constructs discussed in this section.

Assume the following class has been defined:

    template <typename Type>    class Outer    {        public:            template <typename InType>            class Inner            {                public:                    template <typename X>                    void nested();            };    };

The class templateOuter defines a nested class templateInner.Inner in turn defines a member template function.

Next a class templateUsage is defined, offering a member functioncaller expecting an object of the aboveInner type. An initial setupforUsage looks like this:

    template <typename T1, typename T2>    class Usage    {        public:            void caller(Outer<T1>::Inner<T2> &obj);       ...    };

The compiler won't accept this as it interpretsOuter<T1>::Inner as aclass type. But there is no classOuter<T1>::Inner. Here the compilergenerates an error like:

    error: 'class Outer<T1>::Inner' is not a type

There are two ways to inform the compiler thatInner itself is a template, using the template type parameter<T2>. One wayis to prefixcaller's parameter specification withtypename:

    void caller(typename Outer<T1>::Inner<T2> &obj)
Although this works, specifying thatOuter<T1>::Inner<T2> is atypename might looks a bit strange. Therefore. alternatively, the::template construction can be used:
    void caller(Outer<T1>::template Inner<T2> &obj)
::template tells the compiler that the next< must not beinterpreted as a `less than' token, but rather as a template typeargument.

Of course,caller itself is not just declared, it must also beimplemented. Assume that its implementation should callInner's membernested, instantiated for yet another typeX. The class templateUsage therefore receives a third template type parameter, calledT3. Assuming it has been defined byUsage, then to implementcaller, we write:

    template <typename T1, typename T2, typename T3>    void caller(typename Outer<T1>::template Inner<T2> &obj)    {        obj.nested<T3>();    }
Here again we run into a problem. In the function's body the compiler again interprets< as `less than', seeing a logical expression having asits right-hand side a primary expression instead of a function call specifyinga template typeT3.

To tell the compiler that is should interpret<T3> as a type toinstantiate, thetemplate keyword is used, as mentioned by the compiler:

    expected `template' keyword before dependent template name
Although issued as a warning, this warning is then followed by errormessages caused by the compiler interpreting< as `less than'. To removethe warning and the error messages we write.template informingthe compiler that what follows is not a `less than' operator, but atype specification. The function's final implementation thus becomes:
    template <typename T1, typename T2, typename T3>    void caller(typename Outer<T1>::template Inner<T2> &obj)    {        obj.template nested<T3>();    }

Instead of defining value or reference parameters functions may alsodefine pointer parameters. Hadobj been defined as a pointer parameter theimplementation would have had to use the->template construction, ratherthan the.template construction. E.g.,

    template <typename T1, typename T2, typename T3>    void caller(typename Outer<T1>::template Inner<T2> *ptr)    {        ptr->template nested<T3>();    }

As we've seen class templates can be derived from base class templates. Thebase class template can declare astatic member template, which isavailable to a class that is derived from this base class. Such a base classmight look like this:

    template <typename Type>    struct Base    {        template <typename Tp>        static void fun();    };

Normally, when a base class defines a static member we can just call thatmember by prefixing its name by its class name. E.g.,

    int main()    {        Base<int>::fun<double>();    }

This also works fine if a classDerived is derived fromBase,instantiated for a specific type:

    struct Der: public Base<int>    {        static void call()        {            Base<int>::fun<int>();      // OK            fun<int>();                 // also OK        };    };
But when the derived class itself is a class template this way tocallfun won't compile anymore, as it interpretsBase<Type>::fun inBase<Type>::fun<int> as a type, to be instantiated forint. Thisinterpretation can be overruled by indicating thatfun itself is atemplate. For this the::template prefix is used:
    template <typename Type>    struct Der: public Base<Type>    {        //template <typename Tp>    // 'call' may be a member template        //static                    // 'call' may be a static member        void call()        {            // fun<int>();                      // won't compile            // Base<Type>::fun<int>();          // won't compile            Base<Type>::template fun<int>();    // OK            Base<Type>::template fun<Tp>();     // OK if call is a                                                 //    member template        };                                      // (activate typename Tp)    };

23.2: Template Meta Programming

23.2.1: Values according to templates

Intemplate programming values are preferably represented byenumvalues. Enums are preferred over, e.g.,int const values since enums neverrequire any linkage. They are pure symbolic values with no memoryrepresentation whatsoever.

Consider the situation where a programmer must use a cast, say areinterpret_cast. A problem with areinterpret_cast is that it is theultimate way to turn off all compiler checks. All bets are off, and we canwrite extreme but absolutely pointlessreinterpret_cast statements, like

    int intVar = 12;    ostream &ostr = reinterpret_cast<ostream &>(intVar);

Wouldn't it be nice if the compiler would warn us against such oddities bygenerating an error message?

If that's what we'd like the compiler to do, there must be some way todistinguish madness from weirdness. Let's assume we agree on the followingdistinction: reinterpret casts are never acceptable if the target typerepresents a larger type than the expression (source) type, since that wouldimmediately result in exceeding the amount of memory that's actually availableto the target type. For this reason it's clearly silly toreinterpret_cast<double *>(&intVar), butreinterpret_cast<char*>(&intVar) could be defensible.

The intent is now to create a new kind of cast, let's call itreinterpret_to_smaller_cast. It should only be allowed to perform areinterpret_to_smaller_cast if the target type occupies less memory thanthe source type (note that this exactly the opposite reasoning as used byAlexandrescu (2001), section 2.1).

To start, we construct the following template:

    template<typename Target, typename Source>    Target &reinterpret_to_smaller_cast(Source &source)    {        // determine whether Target is smaller than source        return reinterpret_cast<Target &>(source);    }

At the comment an enum-definition is inserted defining a symbol having asuggestive name. A compile-time error results if the required condition isnot met and the error message displays the name of the symbol. A division byzero is clearly not allowed, and noting that afalse value represents azero value, the condition could be:

    1 / (sizeof(Target) <= sizeof(Source));

The interesting part is that this condition doesn't result in any code atall. The enum's value is a plain value that's computed by the compiler whileevaluating the expression:

    template<typename Target, typename Source>    Target &reinterpret_to_smaller_cast(Source &source)    {        enum        {            the_Target_size_exceeds_the_Source_size =                1 / (sizeof(Target) <= sizeof(Source))        };        return reinterpret_cast<Target &>(source);    }

Whenreinterpret_to_smaller_cast is used to cast fromint todouble an error is produced by the compiler, like this:

    error: enumerator value for 'the_Target_size_exceeds_the_Source_size'        is not an integer constant

whereas no error is reported if, e.g.,reinterpret_to_smaller_cast<int>(doubleVar) is requested withdoubleVar defined as adouble.

In the above example anenum was used to compute (at compile-time) avalue that is illegal if an assumption is not met. The creative part isfinding an appropriate expression.

Enum values are well suited for these situations as they do not consumeany memory and their evaluation does not produce any executable code. They canbe used to accumulate values too: the resulting enum value then contains afinal value, computed by the compiler rather than by executable code as thenext sections illustrate. In general, programs shouldn't do run-time what theycan do at compile-time and performing complex calculations resulting in constantvalues is a clear example of this principle.

23.2.1.1: Converting integral types to types

Another use of values buried inside templates is to `templatize' simple scalarint values. This is useful in situations where a scalar value (often abool value) is available to select a specialization but a type is requiredto base the selection on. This situation is shortly encountered (section23.2.2).

Templatizing integral values is based on the fact that a class template together with its template arguments defines a type. E.g.,vector<int> andvector<double> are different types.

Turning integral values into templates is easily done. Define a template (itdoes not have to have any content at all) and store the integral value in anenum:

    template <int x>    struct IntType    {        enum { value = x };    };

AsIntType does not have any members the `class IntType' can bedefined as `struct IntType', saving us from having to typepublic:.

Defining theenum value `value' allows us to retrieve the valueused at the instantiation at no cost in storage. Enum values are neithervariables nor data members and thus have no address. They are mere values.

It's easy to use thestruct IntType. An anonymous or namedobject can be defined by specifying a value for itsint non-typeparameter. Example:

    int main()    {        IntType<1> it;        cout << "IntType<1> objects have value: " << it.value << "\n" <<                "IntType<2> objects are of a different type "                        "and have values " << IntType<2>().value << '\n';    }

Actually, neither the named object nor the anonymous object isrequired. As theenum is defined as a plain value, associated with thestruct IntType we merely have to specify the specificint for whichthestruct IntType is defined to retrieve its `value', like this:

    int main()    {        cout << "IntType<100>, no object, defines `value': " <<                IntType<100>::value << "\n";    }

23.2.2: Selecting alternatives using templates

An essential characteristic of programming languages is that they allow theconditional execution of code. For thisC++ offers theif andswitch statements. If we want to be able to `program the compiler'this feature must also be offered by templates.

Like templates storing values templates making choices do not require any codeto be executed at run-time. The selection is purely made by the compiler, atcompile-time. The essence of template meta programming is that we arenotusing or relying on any executable code. The result of a template metaprogram often is executable code, but that code is a function of decisionsmerely made by the compiler.

Template (member) functions are only instantiated when they are actuallyused. Consequently we can define specializations of functions that aremutually exclusive. Thus it is possible to define a specialization that can becompiled in situation one, but not in situation two and to define anotherspecialization that can be compiled in situation two, but not in situationone. Using specializations code can be generated that is tailored to thedemands of a particular situation.

A feature like this cannot be implemented in run-time executable code. Forexample, when designing a generic storage class the software engineer mayintend to storevalue class type objects as well as objects ofpolymorphic class types in the final storage class. Thus the softwareengineer may conclude that the storage class should contain pointers toobjects, rather than the objects themselves. The initial implementationattempt could look like this:

    template <typename Type>    void Storage::add(Type const &obj)    {        d_data.push_back(            d_ispolymorphic ?                obj.clone()            :                new Type{obj}        );    }

The intent is to use theclone member function of the classTypeifType is a polymorphic class and the standard copy constructor ifType is a value class.

Unfortunately, this scheme usually fails as value classes do not defineclone member functions and polymorphic base classes shoulddeletetheir copy constructors (cf. section7.6). It doesn't matter to thecompiler thatclone is never called for value classes and that the copyconstructor is only available in value classes and not in polymorphicclasses. It merely has some code to compile, and can't do that because ofmissing members. It's as simple as that.

23.2.2.1: Defining overloading members

Template meta programming comes to the rescue. Knowing that class templatemember functions are only instantiated when used, our plan is to designoverloadedadd member functions of which only one is going to be called(and thus instantiated). Our selection will be based on an additional (inaddition toType itself) template non-type parameter that indicateswhether we'll useStorage for polymorphic or non-polymorphic classes. OurclassStorage starts like this:
    template <typename Type, bool isPolymorphic>    class Storage

Initially twooverloaded versions of ouradd member are defined:one used withStorage objects storing polymorphic objects (usingtrueas its template non-type argument) and one storing valueclass objects (usingfalse as its template non-type argument).

Unfortunately we run into a small problem: functions cannot be overloadedby their argumentvalues but only by their argumenttypes. But thesmall problem may be solved. Realizing that types are defined by thecombination of template names and their template arguments we may convert thevaluestrue andfalse into types using the knowledge from section23.2.1.1 about how to convert integral values to types.

We'll provide one (private)add member with anIntType<true> parameter(implementing the polymorphic class) and another (private)add member withanIntType<false> parameter (implementing the non-polymorphic class).

In addition to these two private members a third (public) memberadd isdefined calling the appropriate privateadd member by providing anIntType argument, constructed fromStorage's template non-typeparameter.

Here are the implementations of the threeadd members:

    // declared in Storage's private section:    template <typename Type, bool isPolymorphic>    void Storage<Type, isPolymorphic>::add(Type const &obj, IntType<true>)    {        d_data.push_back(obj.clone());    }    template <typename Type, bool isPolymorphic>    void Storage<Type, isPolymorphic>::add(Type const &obj, IntType<false>)    {        d_data.push_back(new Type(obj));    }    // declared in Storage's public section:    template <typename Type, bool isPolymorphic>    void Storage<Type, isPolymorphic>::add(Type const &obj)    {        add(obj, IntType<isPolymorphic>());    }

The appropriateadd member is instantiated and called because aprimitive value can be converted to a type. Each of the possible templatenon-type values is thus used to define an overloaded class template memberfunction.

Since class template members are only instantiated when used only one ofthe overloaded privateadd members is instantiated. Since the other one isnever called (and thus never instantiated) compilation errors are prevented.

23.2.2.2: Class structure as a function of template parameters

Some software engineers have reservations when thinking about theStorage class that uses pointers to store copies of value classobjects. Their argument is that value class objects can very well be stored byvalue, rather than by pointer. They'd rather store value class objects byvalue and polymorphic class objects by pointer.

Such distinctions frequently occur in template meta programming andthe followingstruct IfElse may be used to obtain one of two types, depending on abool selectorvalue.

First define thegeneric form of the template:

    template<bool selector, typename FirstType, typename SecondType>    struct IfElse    {        using type = FirstType;    };

Then define a partial specialization. The specialization representsa specific selector value (e.g.,false) and leaves the remaining typesopen to further specification:

    template<typename FirstType, typename SecondType>    struct IfElse<false, FirstType, SecondType>    {        using type = SecondType;    };

The former (generic) definition associatesFirstType with theIfElse::type type definition, the latter definition (partially specializedfor the logical valuefalse) associatesSecondType with theIfElse::type type definition.

TheIfElse template allows us to define class templates whose dataorganization is conditional to the template's parameters. UsingIfElse theStorage class may definepointers to store copies of polymorphic class type objects andvaluesto store value class type objects:

    template <typename Type, bool isPolymorphic>    class Storage    {        using DataType = typename IfElse<isPolymorphic, Type *, Type>::type;        std::vector<DataType> d_data;        private:            void add(Type const &obj, IntType<true>);            void add(Type const &obj, IntType<false>);        public:            void add(Type const &obj);    }    template <typename Type, bool isPolymorphic>    void Storage<Type, isPolymorphic>::add(Type const &obj, IntType<true>)    {        d_data.push_back(obj.clone());    }    template <typename Type, bool isPolymorphic>    void Storage<Type, isPolymorphic>::add(Type const &obj, IntType<false>)    {        d_data.push_back(obj);    }    template <typename Type, bool isPolymorphic>    void Storage<Type, isPolymorphic>::add(Type const &obj)    {        add(obj, IntType<isPolymorphic>());    }

The above example usesIfElse'stype, defined byIfElse aseitherFirstType orSecondType.IfElse'stype defines theactual data type to use forStorage'svector data type.

The remarkable result in this example is that thedata organization oftheStorage class now depends on its template arguments. Since theisPolymorphic == true situation uses different data types than theisPolymorphic == false situation, the overloaded privateadd memberscan utilize this difference immediately. E.g.,add(Type const &obj,IntType<false>) uses direct copy construction to store a copy ofobjind_vector.

It is also possible to make a selection from multiple types asIfElsestructs can be nested. Realize that usingIfElse never has any effect onthe size or execution time of the final executable program. The final programsimply contains the appropriate type, conditional to the type that'seventually selected.

23.2.2.3: An illustrative example

The next example, definingMapType as a map having plain types orpointers for either its key or value types, illustrates this approach:
    template <typename Key, typename Value, int selector>    class Storage    {        using MapType =                  typename IfElse<                    selector == 1,              // if selector == 1:                    map<Key, Value>,            // use map<Key, Value>                    typename IfElse<                        selector == 2,          // if selector == 2:                        map<Key, Value *>,      // use map<Key, Value *>                        typename IfElse<                            selector == 3,      // if selector == 3:                            map<Key *, Value>,  // use map<Key *, Value>                                                // otherwise:                            map<Key *, Value *> // use map<Key *, Value *>                        >::type                    >::type                >::type;        MapType d_map;        public:            void add(Key const &key, Value const &value);        private:            void add(Key const &key, Value const &value, IntType<1>);           ...    };    template <typename Key, typename Value, int selector>    inline void Storage<selector, Key, Value>::add(Key const &key,                                                   Value const &value)    {        add(key, value, IntType<selector>());    }

The principle used in the above examples is: if class templates may usedata types that depend on template non-type parameters, anIfElse structcan be used to select the appropriate data types. Knowledge about the variousdata types may also be used to define overloaded member functions. Theimplementations of these overloaded members may then be optimized to thevarious data types. In programs only one of these alternate functions (the onethat is optimized to the actually used data types) will then be instantiated.

The privateadd functions define the same parameters as the publicadd wrapper function, but add a specificIntType type, allowing thecompiler to select the appropriate overloaded version based on the template'snon-type selector parameter.

23.2.3: Templates: Iterations by Recursion

As there are no variables in template meta programming, there is notemplate equivalent to afor orwhile statement. However, iterationscan always be rewritten as recursions. Recursionsare supportedby templates and so iterations can always be implemented as (tail) recursions.

To implement iterations by (tail) recursion do as follows:

The compiler selects a more specialized template implementation over amore generic one. By the time the compiler reaches the end-condition therecursion stops since the specialization does not use recursion.

Most readers are probably familiar with the recursive implementation ofthe mathematical `factorial' operator, commonly represented by theexclamation mark (!):n factorial (so:n!) returns the successiveproductsn * (n - 1) * (n - 2) * ... * 1, representing the number of waysn objects can be permuted. Interestingly, the factorial operator is itselfusually defined by arecursive definition:

    n! = (n == 0) ?            1        :            n * (n - 1)!

To computen! from a template, a templateFactorial can be definedusing anint n template non-type parameter. A specialization is definedfor the casen == 0. The generic implementation uses recursion accordingto the factorial definition. Furthermore, theFactorial template definesanenum value `value' containing its factorial value. Here is thegeneric definition:

    template <int n>    struct Factorial    {        enum { value = n * Factorial<n - 1>::value };    };

Note how the expression assigning a value to `value' uses constantvalues that can be determined by the compiler. The value n is provided, andFactorial<n - 1> is computed usingtemplate metaprogramming.Factorial<n-1> in turn results in value that can bedetermined by the compiler (viz.Factorial<n-1>::value).Factorial<n-1>::value represents thevaluedefined by thetypeFactorial<n - 1>. It isnot the value returnedby anobject of that type. There are no objects here but merely valuesdefined by types.

The recursion ends in a specialization. The compiler selects thespecialization (provided for the terminating value 0) instead of the genericimplementation whenever possible. Here is the specialization's implementation:

    template <>    struct Factorial<0>    {        enum { value = 1 };    };

TheFactorial template can be used to determine, compile time, thenumber of permutations of a fixed number of objects. E.g.,

    int main()    {        cout << "The number of permutations of 5 objects = " <<                Factorial<5>::value << "\n";    }

Once again,Factorial<5>::value isnot evaluated at run-time, butat compile-time. The run-time equivalent of the abovecout statement is,therefore:

    int main()    {        cout << "The number of permutations of 5 objects = " <<                120 << "\n";    }

23.3: User-defined literals

In addition to the literal operators discussed in section11.14C++ also offers a function template literal operator, matching theprototype
    template <char ...Chars>    Type operator "" _identifier()

This variadic non-type parameter function template defines no parameters,but merely a variadic non-type parameter list.

Its argument must be an int constant, as is also expected by the literaloperator defining anunsigned long long int parameter. All the charactersof the int constant are passed as individualchar non-type templatearguments to the literal operator.

For example, if_NM2km is a literal operator function template, it canbe called as80_NM2km. The function template is then actually called as_NM2km<'8', '0'>(). If this function template merely uses template metaprogramming techniques and only processes integral data then its actions canbe performed completely at compile-time. To illustrate this, let's assumeNM2km only processes and returns unsigned values.

The function template_NM2km can forward its argument to a class template,defining an enum constantvalue, and that performs the requiredcomputations. Here is the implementation of the variadic literal operatorfunction template_NM2km:

    template <char ... Chars>    size_t constexpr operator "" _NM2km()    {        return static_cast<size_t>(             // forward Chars to NM2km                NM2km<0, Chars ...>::value * 1.852);    }

The class templateNM2km defines three non-type parameters:accaccumulates the value,c is the first character of the variadic non-typeparameters, while...Chars represents the remaining non-type parameters,contained in a non-type parameter pack. Sincec is, at each recursivecall, the next character from the original non-type parameter pack, the valueso far multiplied by 10 plus the value of the next character is passed as thenext accumulated value to its recursive call, together with the remainingelements of the parameter pack, represented byChars ...:

    template <size_t acc, char c, char  ...Chars>    struct NM2km    {        enum        {            value = NM2km<10 * acc + c - '0', Chars ...>::value        };    };

Eventually, the parameter pack is empty. For this case a partialspecialization ofNM2km is available:

    template <size_t acc, char c>   // empty parameter pack    struct NM2km<acc, c>    {        enum        {            value = 10 * acc + c - '0'        };    };

This works fine, but of course not in cases where binary, octal, orhexadecimal values must also be interpreted. In that case we must firstdetermine whether the first character(s) indicate a special numbersystem. This can be determined by the class templateNM2kmBase, that isnow called from the_NM2km literal operator:

    template <char ... Chars>    size_t constexpr operator "" _NM2km()    {        return static_cast<size_t>(         // forward Chars to NM2kmBase                NM2kmBase<Chars ...>::value * 1.852);    }

TheNM2kmBase class template normally assumes the decimal numbersystem, passing base value 10 and initial sum 0 toNM2km. TheNM2kmclass template is provided with an additional (first) non-type parameterrepresenting the base value of the number system to use. Here isNM2kmBase:

    template <char ...Chars>    struct NM2kmBase    {        enum        {            value = NM2km<10, 0, Chars ...>::value        };    };

Partial specializations handle the different number systems, by inspectingthe first (one or two) characters:

    template <char ...Chars>    struct NM2kmBase<'0', Chars ...>        // "0..."    {        enum        {                                   // octal value: base 8            value = NM2km<8, 0, Chars ...>::value        };    };    template <char ...Chars>    struct NM2kmBase<'0', 'b', Chars ...>   // "0b..."    {        enum        {                                   // binary value: base 2            value = NM2km<2, 0, Chars ...>::value        };    };    template <char  ...Chars>    struct NM2kmBase<'0', 'x', Chars ...>   // "0x..."    {        enum        {                                   // hex value: base 16            value = NM2km<16, 0, Chars ...>::value        };    };

NM2km is implemented as before, albeit that it can now handlevarious number systems. The conversion from character to numeric value is leftto a small support function template,cVal:

    template <char c>    int constexpr cVal()    {        return '0' <= c <= '9' ? c - '0' : 10 + c - 'a';    }    template <size_t base, size_t acc, char c, char  ...Chars>    struct NM2km    {        enum        {            value = NM2km<base, base * acc + cVal<c>(),                                        Chars ...>::value        };    };    template <size_t base, size_t acc, char c>    struct NM2km<base, acc, c>    {        enum { value = base * acc + cVal<c>() };    };

23.4: Template template parameters

Consider the following situation: a software engineer is asked to design astorage classStorage. Data stored inStorage objects may either makeand store copies of the data or store the data as received.Storageobjects may also either use a vector or a linked list as its underlyingstorage medium. How should the engineer tackle this request? Should fourdifferentStorage classes be designed?

The engineer's first reaction could be to develop an all-inStorage class.It could have two data members, a list and a vector, and its constructor couldbe provided with maybe an enum value indicating whether the data itself or newcopies should be stored. The enum value can be used to initialize a series ofpointers to member functions performing the requested tasks (e.g., using avector to store the data or a list to store copies).

Complex, but doable. Then the engineer is asked to modify the class: in thecase of new copies a custom-made allocation scheme should be used rather thanthe standardnew operator. He's also asked to allow the use of yetanother type of container, in addition to the vector and the list that werealready part of the design. Maybe adeque would be preferred or maybe evenastack.

It's clear that the approach aiming at implementing all functionality and allpossible combinations in one class doesn't scale. The classStorage soonbecomes a monolithic giant which is hard to understand, maintain, test, anddeploy.

One of the reasons why the big, all-encompassing class is hard to deploy andunderstand is that a well-designed class shouldenforce constraints: the design of the class should, by itself,disallow certain operations, violations of which should be detected by thecompiler, rather than by a program that might terminate in a fatal error.

Think about the above request. If the class offers both an interface to accessthe vector data storageand an interface to access the list data storage,then it's likely that the class offers an overloadedoperator[] memberto access elements in the vector. This member, however, will be syntacticallypresent, but semantically invalid when thelist data storage is selected,which doesn't supportoperator[].

Sooner or later,users of the monolithic all-encompassing classStorage will fall into the trap of usingoperator[] even thoughthey've selected the list as the underlying data storage. The compiler won'tbe able to detect the error, which only appears once the program isrunning, confusing its users.

The question remains: how should the engineer proceed, when confronted withthe above questions? It's time to introducepolicies.

23.4.1: Policy classes - I

Apolicy defines (in some contexts: prescribes) a particular kind ofbehavior. InC++ apolicy class defines a certain part of the class interface. It may alsodefine inner types, member functions, and data members.

In the previous section the problem of creating a class that might use any ofa series of allocation schemes was introduced. These allocation schemes alldepend on the actual data type to use, and so the `template reflex' shouldkick in.

Allocation schemes should probably be defined as template classes, applyingthe appropriate allocation procedures to the data type at hand. When suchallocation schemes are used by the familiar STL containers (likestd::vector, std::stack, etc.), then such home-made allocation schemesshould probably be derived fromstd::allocator, to providefor the requirements made by these containers. The class templatestd::allocator is declared by the<memory> header file and the threeallocation schemes developed here were all derived fromstd::allocator.

Using in-class implementations for brevity the following allocation classescould be defined:

The above three classes definepolicies that may be selected by theuser of the classStorage introduced in the previous section. In additionto these classes, additional allocation schemes could be implemented by theuser as well.

To apply the proper allocation scheme to the classStorage,Storage should be designed as a class template itself. The class alsoneeds a template type parameter allowing users to specify the data type.

The data type to be used by a particular allocation scheme could of coursebe specified when specifying the allocation scheme to use. The classStorage would then have two template type parameters, one for the datatype, one for the allocation scheme:

    template <typename Data, typename Scheme>    class Storage ...

To use the classStorage we would then write, e.g.:

    Storage<string, NewAlloc<string>> storage;

UsingStorage this way is fairly complex and potentially error-prone,as it requires the user to specify the data type twice. Instead, theallocation scheme should be specified using a new type of template parameter,not requiring the user to specify the data type required by the allocationscheme. This new kind of template parameter (in addition to the well-knowntemplate type parameter andtemplate non-type parameter) is called thetemplate template parameter.

Starting with the C++14 standard the keywordclass in the syntactical form of template template parameters (template<parameter specifications> class Name) is no longer required. From thatstandard onward, the keywordtypename can also be used (e.g.,template<parameter specifications> typename Name).

23.4.2: Policy classes - II: template template parameters

Template template parameters allow us tospecify aclass template as a template parameter. By specifying a classtemplate, it is possible to add a certain kind of behavior (called apolicy) to an existing class template.

To specify an allocationpolicy, rather than an allocationtypefor the classStorage we rephrase its class template header:definition starts as follows:

    template <typename Data, template <typename> class Policy>    class Storage...

The second template parameter is new. It is atemplate templateparameter. It has the following elements:

Policy classes are often an integral part of the class underconsideration. Because of this they are often deployed as base classes. In theexample the classPolicy could be used as a base class of the classStorage.

The policy operates on the classStorage's data type. Therefore thepolicy is informed about that data type as well. Our classStorage nowbegins like this:

    template <typename Data, template <typename> class Policy>    class Storage: public Policy<Data>

This automatically allows us to usePolicy's members when implementingthe members of the classStorage.

Our home-made allocation classes do not really provide us with many usefulmembers. Except for the extraction operator they offer no immediate access tothe data. This can easily be repaired by adding more members. E.g., the classNewAlloc could be augmented with operators allowing access to andmodification of stored data:

        operator Data &()   // optionally add a `const' member too        {            return *d_data;        }        NewAlloc &operator=(Data const &data)        {            *d_data = data;        }

The other allocation classes could be given comparable members.

Let's use the allocation schemes in some real code. The nextexample shows howStorage can be defined using some data type and anallocation scheme. We start out again with a classStorage:

    template <typename Data, template <typename> class Allocate>    class Storage: public std::vector<Data, Allocate<Data>>    {};

That's all we have to do. Note thatstd::vector formally has twotemplate parameters. The first one is the vector's data type, which is alwaysspecified; the second one is the allocator used by the vector. Usually theallocator is left unspecified (in which case the default STL allocator isused), but here it is mentioned explicitly, allowing us to pass our ownallocation policy toStorage.

All required functionality is inherited from thevector base class, whilethe policy is `factored into the equation' using a template templateparameter. Here's an example showing how this is done:

    Storage<std::string, NewAlloc> storage;    copy(istream_iterator<std::string>(cin), istream_iterator<std::string>(),            back_inserter(storage));    cout << "Element index 1 is " << storage[1] << '\n';    storage[1] = "hello";    copy(storage.begin(), storage.end(),         ostream_iterator<NewAlloc<std::string> >(cout, "\n"));

SinceStorage objects are alsostd::vector objects the STLcopyfunction can be used in combination with theback_inserter iterator to addsome data to thestorage object. Its elements can be accessed and modifiedusing the index operator. ThenNewAlloc<std::string> objects are insertedintocout (also using thecopy function).

Interestingly, this is not the end of the story. Remember that ourintention was to create a class allowing us to specify thestorage type aswell. What if we don't want to use avector, but instead would like to usealist?

It's easy to changeStorage's setup so that a completely differentstorage type can be used on request, like adeque. To implement this, thestorage class is parameterized as well, using yet another template templateparameter:

    template <typename Data, template <typename> class AllocationPolicy,              template <typename, typename> class Container = std::vector>    class Storage: public Container<Data, AllocationPolicy<Data>>    {};

The earlier example using aStorage object can be used again withoutrequiring any modifications at all (except for the above redefinition). Itclearly can't be used with alist container, as thelist lacksoperator[]. Trying to do so is immediately recognized by the compiler,producing an error if an attempt is made to useoperator[] on, e.g., alist. (A complete example showing the definition of theallocation classes and the classStorage as well as its use is provided intheC++ Annotations's distribution in the fileyo/advancedtemplates/examples/storage.cc.) Alist container, howevercan still be specified as the container to use. In that case aStorage isimplemented as alist, offeringlist's interface, rather thanvector's interface, to its users.

23.4.2.1: The destructor of Policy classes

In the previous section policy classes were used as base classes of templateclasses. This resulted in the interesting observation that a policy classmay serve as abase class of a derived class. As a policy class may act as a base class, a pointer or reference to sucha policy class can be used to point or refer to the derived class using thepolicy.

This situation, although legal, should be avoided for various reasons:

To avoid these drawbacks, it is good practice toprevent the use ofreferences or pointers to policy classes to refer or point to derivedclass objects. This is accomplished by providing policy classes withnon-virtual protected destructors. With a non-virtual destructor thereis no performance penalty and since its destructor is protected users cannotrefer to classes derived from the policy class using a pointer or reference tothe policy class.

23.4.3: Structure by Policy

Policy classes usually define behavior, not structure. Policy classes are normally used to parameterize some aspect of the behavior of classes thatare derived from them. However, different policies may require different datamembers. These data members may also be defined by the policy classes. Policyclasses may therefore be used to define both behavior and structure.

By providing a well-defined interface a class derived from a policy class maydefine member specializations using the different structures of policy classesto their advantage. For example, a plain pointer-based policy class couldoffer its functionality by resorting toC-stylepointer juggling,whereas avector-based policy class could use the vector's membersdirectly.

In this example a generic class templateSize could be designedexpecting a container-like policy using features commonly found incontainers, defining the data (and hence the structure) of the containerspecified in the policy. E.g.:

    template <typename Data, template <typename> class Container>    struct Size: public Container<Data>    {        size_t size()        {                           // relies on the container's `size()'                                    // note: can't use `this->size()'            return Container<Data>::size();        }    };

A specialization can now be defined for a much simpler storage classusing, e.g., plain pointers (the implementation capitalizes onfirst andsecond, data members ofstd::pair. Cf. the example at the end of thissection):

    template <typename Data>    struct Size<Data, Plain>: public Plain<Data>    {        size_t size()        {                           // relies on pointer data members            return this->second - this->first;        }    };

Depending on the intentions of the template's author other members couldbe implemented as well.

To simplify the real use of the above templates, a generic wrapper classcan be constructed: it uses theSize template matching the actually usedstorage type (e.g., astd::vector or some plain storage class) to defineits structure:

    template <typename Data, template <typename> class Store>    class Wrapper: public Size<Data, Store>    {};

The above classes could now be used as follows (en passant showing anextremely basicPlain class):

    #include <iostream>    #include <vector>    template <typename Data>    struct Plain: public std::pair<Data *, Data *>    {};    int main()    {        Wrapper<int, std::vector> wiv;        std::cout << wiv.size() << "\n";        Wrapper<int, Plain> wis;        std::cout << wis.size() << "\n";    }

Thewiv object now defines vector-data, thewis object merelydefines astd::pair object's data members.

23.5: Alias Templates

In addition to function and class templates,C++ also uses templates todefine an alias for a set of types. This is called analias template. Alias templates can be specialized. The name of analias template is a type name.

Alias templates can be used as arguments to template template parameters. Thisallows us to avoid the `unexpected default parameters' you may encounter(e.g., when usingC++ standards before thec++23 standard) when usingtemplate template parameters. Defining a template specifying atemplate<typename> class Container is fine, but (beforec++23) it was impossibleto specify a container likevector orset as template templateargument, asvector andset containers also define second templateparameters, specifying their allocation policies.

Alias templates are defined likeusing declarations, specifying an aliasfor an existing (maybe partially or fully specialized) template type. In thefollowing exampleVector is defined as an alias forvector:

    template <typename Type>    using Vector = std::vector<Type>;    Vector<int> vi;             // same as std::vector<int>    std::vector<int> vi2(vi);   // copy construction: OK

So, what's the point of doing this? Looking at thevector container, we see that it defines two, ratherthan one, template parameters, the second parameter being the allocationpolicy_Alloc, by default set tostd::allocator<_Tp>:

    template<typename _Tp, typename _Alloc = std::allocator<_Tp> >    class vector: ...

Now define a class templateGeneric defining a template templateparameter:

    template <typename Type, template <typename> class Container>    class Generic: public Container<Type>    {        ...    };

Most likely,Generic offers members made available by the containerthat is actually used to create theGeneric object, and adds to those somemembers of it own. However, a simple container likestd::vector cannot beused, asstd::vector doesn't match atemplate <typename> classContainer> parameter; it requires atemplate <typename, typename> classContainer> template template parameter.

TheVector alias template, however,is defined as a templatehaving one template type parameter, and it uses the vector's defaultallocator. Consequently, passing aVector toGeneric works fine:

    Generic<int, Vector> giv;       // OK    Generic<int, std::vector> err;  // won't compile: 2nd argument mismatch

With the aid of a small alias template it is also possible to use acompletely different kind of container, like amap, withGeneric:

    template <typename Type>    using MapString = std::map<Type, std::string>;    Generic<int, MapString> gim;    // uses map<int, string>

23.6: Trait classes

Scattered over thestd namespacetrait classes are found. E.g., mostC++ programmers have seenthe compiler mentioning `std::char_traits<char>' when performing anillegal operation onstd::string objects, as instd::string s(1).

Trait classes are used to make compile-time decisions about types. Traitclasses allow us to apply the proper code to the proper data type, be it apointer, a reference, or a plain value, all this maybe in combination withconst. The particular type of data to use can be inferred from the actualtype that is specified (or implied) when the template is used. This can befully automated, not requiring the template writer to make any decision.

Trait classes allow us to develop atemplate <typename Type1, typenameType2, ...> without the need to specify many specializations covering allcombinations of, e.g., values, (const) pointers, or (const) references, whichwould soon result in an unmaintainable exponential explosion of templatespecializations (e.g., allowing these five different types for each templateparameter already results in 25 combinations when two template type parametersare used: each must be covered by potentially different specializations).

Having available a trait class, the actual type can be inferred compiletime, allowing the compiler to deduce whether or not the actual type is apointer, a pointer to a member, or a const pointer, and to make comparabledeductions in case the actual type is, e.g., an lvalue or rvalue referencetype. This in turn allows us to write templates that define types likeargument_type,first_argument_type,second_argument_type andresult_type, which are required by several generic algorithms (e.g.,count_if()).

A trait class usually performs no behavior. I.e., it has no constructorand no members that can be called. Instead, it defines a series of types andenum values that have certain values depending on the actual type that ispassed to the trait class template. The compiler uses one of a set ofavailable specializations to select the one appropriate for an actual templatetype parameter.

The point of departure when defining a trait template is a plainvanillastruct, defining the characteristics of a plain value type likeanint. This sets the stage for specific specializations, modifying thecharacteristics for any other type that could be specified for the template.

To make matters concrete, assume the intent is to create a trait classBasicTraits telling us whether a type is a plain value type, a pointertype, an lvalue reference type or an rvalue reference type (all of which mayor may not beconst types).

Whatever the actually provided type, we want to be able to determine the`plain' type (i.e., the type without any modifiers, pointers or references),the `pointer type' and the `reference type', allowing us to define in allcases, e.g., an rvalue reference to its built-in type, even though we passed aconst pointer to that type.

Our point of departure, as mentioned, is a plainstruct defining therequired parameter. Maybe something like this:

        template <typename T>        struct Basic        {            using Type = T;            enum            {                isPointer = false,                isConst = false,                isRef = false,                isRRef = false            };        };

Although some conclusions can be drawn by combining variousenumvalues (e.g., a plain type is not a pointer or a reference or an rvaluereference or a const), it is good practice to provide a full implementation oftrait classes, not requiring its users to construct these logical expressionsthemselves. Therefore, the basic decisions in a trait class are usually madeby anested trait class, leaving the task of creating appropriate logical expressions to asurrounding trait class.

So, thestruct Basic defines the generic form of our inner traitclass. Specializations handle specific details. E.g., a pointer type isrecognized by the following specialization:

        template <typename T>        struct Basic<T *>        {            using Type = T;            enum            {                isPointer = true,                isConst = false,                isRef = false,                isRRef = false            };        };

whereas a pointer to a const type is matched with the next specialization:

        template <typename T>        struct Basic<T const *>        {            using Type = T;            enum            {                isPointer = true,                isConst = true,                isRef = false,                isRRef = false            };        };

Several other specializations should be defined: e.g., recognizingconst value types or (rvalue) reference types. Eventually all thesespecializations are implemented as nestedstructs of an outer classBasicTraits, offering the public traits class interface. The outline ofthe outer trait class is:

    template <typename TypeParam>    class BasicTraits    {        // Define specializations of the template `Basic' here        public:            BasicTraits(BasicTraits const &other) = delete;            using ValueType = Basic<TypeParam>::Type;            using PtrType = ValueType *;            using RefType = ValueType &;            using RvalueRefType = ValueType &&;            enum            {                isPointerType = Basic<TypeParam>::isPointer,                isReferenceType = Basic<TypeParam>::isRef,                isRvalueReferenceType = Basic<TypeParam>::isRRef,                isConst = Basic<TypeParam>::isConst,                isPlainType = not (isPointerType or isReferenceType or                                   isRvalueReferenceType or isConst)            };    };

The trait class's public interface explicitly deletes its copyconstructor. As it therefore defines no constructor at all and as it has nostatic members it does not offer any run-time executable code.All the trait class's facilities must therefore be used compile time.

A trait class template can be used to obtain the proper type, irrespectiveof the template type argument provided. It can also be used to selecta proper specialization that depends on, e.g., theconst-ness of atemplate type. Example:

     cout <<      "int: plain type? "     << BasicTraits<int>::isPlainType << "\n"      "int: ptr? "            << BasicTraits<int>::isPointerType << "\n"      "int: const? "          << BasicTraits<int>::isConst << "\n"      "int *: ptr? "          << BasicTraits<int *>::isPointerType << "\n"      "int const *: ptr? "    << BasicTraits<int const *>::isPointerType <<                                                                      "\n"      "int const: const? "    << BasicTraits<int const>::isConst << "\n"      "int: reference? "      << BasicTraits<int>::isReferenceType << "\n"      "int &: reference? "    << BasicTraits<int &>::isReferenceType << "\n"      "int const &: ref ? "   << BasicTraits<int const &>::isReferenceType <<                                                                        "\n"      "int const &: const ? " << BasicTraits<int const &>::isConst << "\n"      "int &&: r-reference? " << BasicTraits<int &&>::isRvalueReferenceType <<                                                                        "\n"      "int &&: const? " << BasicTraits<int &&>::isConst << "\n"      "int const &&: r-ref ? "<< BasicTraits<int const &&>::                                                isRvalueReferenceType << "\n"      "int const &&: const ? "<< BasicTraits<int const &&>::isConst << "\n"        "\n";     BasicTraits<int *>::ValueType           value = 12;     BasicTraits<int const *>::RvalueRefType rvalue = int(10);     BasicTraits<int const &&>::PtrType      ptr = new int(14);     cout << value << ' ' << rvalue << ' ' << *ptr << '\n';

23.6.1: Distinguishing class from non-class types

In the previous section theBasicTraits trait class was developed. Usingspecialized versions of a nestedstruct Type modifiers, pointers,references and values could be distinguished.

Knowing whether a type is a class type or not (e.g., the type represents aprimitive type) could also be a useful bit of knowledge to a templatedeveloper. The class template developer might want to define aspecialization when the template's type parameter represents a classtype (maybe using some member function that should be available)and another specialization for non-class types.

This section addresses the question how a trait class can distinguish class types from non-class types.

In order to distinguish classes from non-class types a distinguishing featurethat can be used at compile-time must be found. It may require some thinking tofind such a distinguishing characteristic, but a good candidate eventually isfound in the pointer to members syntactic construct. Pointers to membersare only available for classes. Using the pointer to member construct as thedistinguishing characteristic, a specialization can be developed that uses thepointer to member if available. Another specialization (or the generictemplate) does something else if the pointer to member construction is notavailable.

How can we distinguish a pointer to a member from `a generic situation',not being a pointer to a member? Fortunately, such a distinction ispossible. A function template specialization can be defined having a parameterwhich is a pointer to a member function. The generic function templatethen accepts any other argument. The compilerselects the former(specialized) function when the provided type is a class type as class typesmay support a pointer to a member. The interesting verb here is `may':the class does nothave to define a pointer to member.

Furthermore, the compiler does not actuallycall any function: we'retalking compile-time here. All the compiler does is toselect theappropriate function by evaluating a constant expression.

So, our intended function template now looks like this:

    template <typename ClassType>    static `some returntype'  fun(void (ClassType::*)());

The function's return type (`(some returntype)') will be definedshortly. Let's first have a closer look at the function's parameter. Thefunction's parameter defines a pointer to a member returningvoid. Such afunction doesnot have to exist for the concrete class-type that'sspecified when the function is used. In fact,no implementation isprovided. The functionfun is only declared as astatic member in thetrait class. It's not implemented and no trait class object is required tocall it. What, then, is its use?

To answer the question we now have a look at the generic functiontemplate that should be used when the template's argument isnot a classtype. The language offers a `worst case' parameter in itsellipsis parameter list. The ellipsis is a final resort the compilermay turn to if everything else fails. The generic function template specifiesa plain ellipsis in its parameter list:

    template <typename NonClassType>    static `some returntype' fun(...);

It would be an error to define the generic alternative as a functionexpecting anint. The compiler, when confronted with alternatives,favors the simplest, most specified alternative over a more complex, genericone. So, when providingfun with an argument it selectsintwhenever possible and it won't selectfun(void (ClassType::*)()). Whengiven the choice betweenfun(void (ClassType::*)()) andfun(...) itselects the former unless it can't do that.

The question now becomes: what argument can be used for both a pointer toa member and for the ellipsis? Actually, there is such a `one size fits all'argument: 0. The value 0 can be passed as argument value to functions definingan ellipsis parameter and to functions defining a pointers-to-memberparameter.

But 0 does not specify a particular class. Therefore,fun must specifyan explicit template argument, appearing in our code asfun<Type>(0), withType being the template type parameter of the trait class.

Now for the return type. The function's return type cannot be a simplevalue (liketrue orfalse). Our eventual intent is to provide thetrait class with an enum telling us whether the trait class's templateargument represents a class type or not. That enum becomes something likethis:

    enum { isClass = some class/non-class distinguishing expression } ;

The distinguishing expression cannot be

    enum { isClass = fun<Type>(0) } ;

asfun<Type>(0) is not a constant expression and enum valuesmust be defined by constant expressions so they can be determined at compile-time.

To determineisClass's value we must find an expression allowing forcompile-time discriminations betweenfun<Type>(...) andfun<Type>(void(Type::*)()).

In situations like these thesizeof operator often is our tool ofchoice as it is evaluated at compile-time. By defining different sized returntypes for the twofun declarations we are able to distinguish (atcompile-time) which of the twofun alternatives is selected by thecompiler.

Thechar type is by definition a type having size 1. By defininganother type containing two consecutivechar values a larger type isobtained. Achar [2] is of course not a type, but achar[2] can bedefined as a data member of a struct, and a structdoes define atype. That struct then has a size exceeding 1. E.g.,

    struct Char2    {        char data[2];    };

Char2 can be defined as a nested type of our traits class. Thetwofun function template declarations become:

    template <typename ClassType>    static Char2 fun(void (ClassType::*)());    template <typename NonClassType>    static char fun(...);

Sincesizeof expressions are evaluated at compile-time we can nowdetermineisClass's value:

    enum { isClass = sizeof(fun<Type>(0)) == sizeof(Char2) };

This expression has several interesting implications:

Without requiring any instantiation the trait class can now provide ananswer to the question whether a template type argument represents a classtype or not. Marvelous!

23.6.2: Available type traits

C++ offers many facilities to identify and modifiy characteristics oftypes. Before using these facilities the<type_traits> header file must beincluded.

All facilities offered bytype_traits are defined in thestdnamespace (omitted from the examples given below), allowing programmers todetermine various characteristics of types and values.

In the description of available type traits the following concepts areencountered:

Whentype-condition applies to a type, it must be a complete type,void, or an array of unknown size;

The following type traits are provided:

23.7: Defining `ErrorCodeEnum' and 'ErrorConditionEnum' enumerations

In section4.3.1 the classstd::error_code was introduced. One ofits constructors acceptsErrorCodeEnum values, whereErrorCodeEnum isa template type name for enumerations that we may define ourselves containingsymbols that are used as error code values. Another constructor expects anint-valued error code and a specification of an error category that usesthose error codes.

Several error code enumerations and error categories are predefined byC++but it is also possible to define newErrorCodeEnums and errorcategories. In this section constructing newErrorCodeEnums is covered, inthe next section designing new error categories is covered.

Defining new error code enumerations is an option when usingerror_codeobjects is attractive, but standard error code values (like the values definedbyenum class errc) aren't appropriate. For example, when designing aninteractive calculator, several errors may be encountered that are related tothe way expressions are entered by the user. For those situations you mightwant to develop your own error code enumeration.

In this and the next section a bare bones approach to defining error codeenumerations and error categories is adopted. No concrete, real-life likeclass is developed. I think the advantage of this is that this way it's easierto apply the principles to new real-life situations than if you first have toabstract the content of a real-life example yourself. Here we go:

This completes the definition of our own error code enumeration, whose symbolsare now accepted byerror_code's constructor.

Before we're able to design our own error category we must also have a look at`higher order' causes of errors as represented by objects of the classstd::error_condition (cf. section10.9.2). Error conditionsrepresent platform independent errors like syntax errors or non-existingrequests.

In our bare bones implementation of an error category these higher ordercauses of errors are enumerated in theenum class Cond enumeration. It'sdefined similarly toCatErr.

We're now ready for designing our ownerror_category class.

23.7.1: Deriving classes from std::error_category

In the previous section the error code enumerationsCatErr andCondwere developed. The values of these enumerations specify, respectively, thedirect and the platform independent causes of errors that may be encounteredin the context of the new error category developed in this section.

Classes derived fromstd::error_category are designed as singleton classesand implement their ownname, message and anequivalent members. OurclassCategory also declares a static memberinstance returning areference to the class's singleton object, which is compile-time initializedand is available by the timeinstance is called. Alternatively a dedicatedfunction (likeCategory_category), analogously to the functiongeneric_category, returning a reference to theCategory object couldbe defined.

CatErr values,Cond values and textual descriptions ofCatErr'svalues are combined in astd::unordered_map usingCatErr as key, and astruct POD as value type. This map allows us to retrieve the platformindependent error types and the descriptions that are associated withCatErr values.

Here is the interface of the classCategory:

    class Category: public std::error_category    {        static Category s_instance;        struct POD        {            Cond        cond;            char const *msg;        };            static std::unordered_map<CatErr, POD> s_map;            public:            Category(Category const &other) = delete;                static Category &instance();                bool equivalent(std::error_code const &ec, int condNr)                                                const noexcept override;            bool equivalent(int ev, std::error_condition const &condition)                                                const noexcept override;            std::error_condition default_error_condition(int ev)                                                const noexcept override;                std::string message(int ce) const override;            char const *name() const noexcept override;            private:            Category() = default;                template <typename Enum>            static constexpr Enum as(int err);    };

Itsunordered_map s_map provides theCond values and verbaldescriptions of theCatErr values given thoseCatErr values:

    unordered_map<CatErr, Category::POD> Category::s_map =    {        { CatErr::Err1, { Cond::Cond1, "Err1" } },        { CatErr::Err2, { Cond::Cond2, "Err2" } },        { CatErr::Err3, { Cond::Cond1, "Err3" } },    };

The functionsmake_error_code andmake_error_condition return,respectively,error_code anderror_condition objects from,respectively,CatErr values andCond values.

Their declarations can be provided below theCategory class interface andtheir implementations pass theCategory object to their constructors:

    std::error_code make_error_code(CatErr ce)    {        return { static_cast<int>(ce), Category::instance() };    }

    std::error_condition make_error_condition(Cond ec)    {        return { static_cast<int>(ec), Category::instance() };    }

The membernamemust be defined by classes derived fromerror_category. It simply returns a short string naming the category(e.g.,"Category" for ourCategory class). Likewise, the membermessagemust be redefined. Its implementation usually is slightly morecomplex thanname's implementation: it expects a (cast to anint)CatErr value and uses that value to find the corresponding textualdescription ins_map. If found the description is returned; if not foundthen a short fallback message is returned:

    std::string Category::message(int ce) const    {        auto iter = s_map.find(static_cast<CatErr>(ce));        return iter != s_map.end() ? iter->second.msg : "No CatErr value";    }

The memberdefault_error_condition receives a (cast toint)CatErrvalue. That value is used to find the associatedCond value. If theint received by the function does not represent a validCatErr valuethen the fallback valueCond::NoCond is used. The function returns anerror_condition object created bymake_error_condition which receivesthe determinedCond value as its argument:

    std::error_condition Category::default_error_condition(int ev)                                                            const noexcept    {        auto iter = s_map.find(as<CatErr>(ev));            return make_error_condition(                    iter == s_map.end() ? Cond::NoCond : iter->second.cond                );    }

What's left is implementing the twoequivalent members. The firstequivalent member (receiving a reference to anerror_code object and a(cast toint)Cond value) determines the equivalence of theCond value that is associated with theerror_code objectand theCond value that is specified as the function's second argument. If these values are equal and theerror_code object's category is equaltoCategory then the equivalence has been established andtrue isreturned. Here is its implementation:

    bool Category::equivalent(std::error_code const &ec, int condNr)                                                            const noexcept    {        if (*this != ec.category())            return false;            if (ec.value() == 0)                            // no error in ec?            return condNr == 0;                         // then condNr must                                                        // also be 0            auto iter = s_map.find(as<CatErr>(ec.value())); // info associated                                                        // with ec's CatErr            return iter == s_map.end() ?                            false                       // not found or                        :                               // compare Cond values                            iter->second.cond == as<Cond>(condNr);    }

The secondequivalent member (receiving (as anint)CatErr value and anerror_condition object) determines the equivalence of anerror_condition object that is constructed from theCond value that is associated with theCatErr value that was passed (asint) to the functionand theerror_condition object that was passed to the function as its second argument.

Here a prerequisite for concluding equivalence is that the error condition'scategory isCategory. If that's the case then the function returnstrue if itsint argument equals zero and thecondition object alsoindicates no error. Alternatively, if thecondition argument is equal totheerror_condition object made from theCond value associated withtheCatErr value passed to the function as its first argument theequivalence has also been established andtrue is returned. Here is itsimplementation:

    bool Category::equivalent(int ev, error_condition const &condition)                                                            const noexcept    {        if (ev == 0)                                    // no error? then            return condition.category() == *this and    // categories must                   not static_cast<bool>(condition);    // be equal and                                                        // condition must                                                        // indicate no error            auto iter = s_map.find(as<CatErr>(ev));     // find ev's Cond            return iter == s_map.end()?                    false                           // no such CatErr                :                                   // or compare conditions                    condition == make_error_condition(iter->second.cond);    }

So, in order to define your own category:

23.8: Using `noexcept' when offering the `strong guarantee'

When throwing exceptions while trying to achieve the strong guarantee afunction's actions are usually split in two parts

The actions in the first step might be mademove aware by usingstd::move (e.g., to assign the source's values to a (possibly temporary)destination). However, usingstd::move can easily affect the source (e.g.,when extending the source's memory, moving the existing data to its newlocations), which breaks the first step's assumption, as the target object isnow modified.

In this case (and generally) the move operation should not be allowed to throwexceptions. This, in turn, implies that it is difficult to write code whichmust offer a non-throwing moving constructor, if it uses (external) data types over which the moving constructor has no control. E.g.,

    template <typename Type>    class MyClass    {        Type d_f;        public:            MyClass() = default;            MyClass(MyClass &&tmp)            :                d_f(move(tmp.d_f))            {}    };

Here,MyClass's author has no control over the design ofType. IfMyClass is instantiated with the typeForeign, merely having a(possibly throwing) copy constructor, then the following code breaks theno-throw assumption underlying move constructors:

    MyClass<Foreign> s2{ move(MyClass<Foreign>()) };

If templates are able to detect whetherType has non-throwing moveconstructors then their implementations may be optimized bycalling these move constructors (already modifying their targets in the firstpart of code offering the strong guarantee) in situations where otherwise thenon-modifying, but more expensive copy constructor has to be used.

Thenoexcept keyword was introduced to allow such templates to performsuch optimizations. As withthrow lists, checking fornoexcept isa run-time check, but the consequence of violating anoexept declarationare more serious than violating athrow list: violatingnoexceptresults in callingstd::terminate, terminating the program, possiblywithout unwinding the stack. In the context of the previous example, thefollowing code is flawlessly accepted by the compiler, demonstrating thatthere is no compile-time checking ofnoexcept:

    class Foreign    {        public:            Foreign() = default;            Foreign(Foreign const &other) noexcept            {                throw 1;            }    };

However, when this class's copy constructor is called, execution abortswith the following message:

    terminate called after throwing an instance of 'int'    Abort

Keep in mind that the current purpose ofnoexcept is to allow templates tooptimize their code by using move operations where the code must also be ableto offer the strong exception guarantee. Sincenoexcept also offers theconditionalnoexcept(condition) syntax (withnoexcept(true) andnoexcept having identical semantics),noexcept can be madeconditional to the `noexcepting' nature of template types. Note that this isnot possible withthrow lists.

The following rules of thumb may be used to decide whetheror not to usenoexcept in your code:

23.9: More conversions to class types

23.9.1: Types to types

Althoughclass templates may be partially specialized,functiontemplates may not. At times this is annoying. Assume a function template isavailable implementing a certain unary operator that could be used with thetransform generic algorithm (cf. section19.1.56):
    template <typename Return, typename Argument>    Return chop(Argument const &arg)    {        return Return{ arg };    }

Furthermore assume that ifReturn isstd::string then the aboveimplementation should not be used. Instead, withstd::string a secondargument1 should always be provided. IfArgument is aC++ string,this would allow us to, e.g., return a copy ofarg from which its firstcharacter has been chopped off.

Sincechop is a function, it is not possible to define a partialspecialization like this:

    template <typename Argument>        // This won't compile!    std::string chop<std::string, Argument>(Argument const &arg)    {        return std::string{ arg, 1 };    }

Although a function template cannot be partially specialized itispossible to use overloading, defining a second, dummy,string parameter:

    template <typename Argument>    std::string chop(Argument const &arg, std::string)    {        return std::string{ arg, 1 };    }

Instead of providing astring dummy argument the functionscoulduse theIntType template (cf. section23.2.1.1) to select the properoverloaded version. E.g.,IntType<0> could be defined as the type of thesecond argument of the first overloadedchop function, andIntType<1>could be used for the second overloaded function. From the point of view ofprogram efficiency this is an attractive option, as the providedIntTypeobjects are extremely lightweight.IntType objects contain no data atall. But there's also an obvious disadvantage as there is no intuitively clearassociation between theint value used and the intended type.

Instead of defining arbitraryIntType types it is more attractive touse another lightweight solution, using an automatic type-to-typeassociation. Thestruct TypeType is a lightweight type wrapper, much likeIntType. Here is its definition:

    template <typename T>    struct TypeType    {        using Type = T;    };

TypeType is also a lightweight type as it doesn't have any data fieldseither.TypeType allows us to use a natural type association forchop's second argument. E.g, the overloaded functions can now be definedas follows:

    template <typename Return, typename Argument>    Return chop(Argument const &arg, TypeType<Argument> )    {        return Return{ arg };    }    template <typename Argument>    std::string chop(Argument const &arg, TypeType<std::string> )    {        return std::string{ arg, 1 };    }

Using the above implementations any type can be specified forResult. If it happens to be astd::string the appropriate overloadedversion is automatically selected. The following additional overload of thefunctionchop capitalizes on this:

    template <typename Result>    Result chop(char const *txt)    // char const * could also be a 2nd    {                               // template type parameter        return chop(std::string{ txt }, TypeType<Result>{});    }

Using the thirdchop function, the following statement produces thetext `ello world':

    cout << chop<string>{ "hello world" } << '\n';

Template functions do not support partial specializations. But they can beoverloaded. By providing overloads with dummy type-arguments that dependon other parameters and calling these overloads from a overloaded functionthat does not require the dummy type argument a situation similar to partialspecializations with class templates can often be realized.

23.9.2: An empty type

At times (cf. section23.10) an emptystruct is a useful tool. It can be used as atype actinganalogously to the final 0-byte in NTBSs.It can simply be defined as:
    struct NullType    {};

23.9.3: Type convertibility

In what situations can a typeT be used as a `stand in' for another typeU? SinceC++ is a strongly typed language the answer is surprisinglysimple:Ts can be used instead ofUs if aT is accepted asargument in cases whereUs are requested.

This reasoning is behind the following class which can be used to determinewhether a typeT can be used where a typeU is expected. Theinteresting part is that no code is actually generated or executed. Alldecisions are made by the compiler.

In the second part of this section we'll show how the code developed inthe first part can be used to detect whether a classB is a base class ofanother classD (theis_base_of template (cf. section23.6.2)also provides an answer to this question). The code developed here closelyfollows the example provided by Alexandrescu (2001,p. 35).

First, a functiontest is designed accepting a typeU.The functiontest returns a valueof the as yet unknown typeConvertible:

    Convertible test(U const &);

The functiontest is never implemented. It is only declared. If a typeT can be used instead of a typeU thenT can also be passed asargument to the abovetest function.

On the other hand, if the alternate typeT cannot be used where aU isexpected, then the compiler won't be able to use the abovetestfunction. Instead, it uses an alternative function that has a lowerselection priority but that canalways be used withanyT type.

C (andC++) offer a very general parameter list, a parameter list thatis always considered acceptable. This parameter list is the familiarellipsis which represents theworst case the compiler mayencounter. If everything else fails, then the function defining an ellipsisas its parameter list is selected.

Usually that's not a productive alternative, but in the current situation itisexactly what is needed. When confronted with two candidate functions,one of which defines an ellipsis parameter, the compiler selects thefunction defining the ellipsis parameter only if the alternative(s) can't beused.

Following the above reasoning an alternative functiontest(...) isdeclared as well. This alternate function does not return aConvertiblevalue but aNotConvertible value:

    NotConvertible test(...);

Iftest's argument is of typeT and ifT can be converted toUthentest's return type isConvertible. OtherwiseNotConvertibleis returned.

This situation clearly shows similarities with the situation encountered insection23.6.1 where the valueisClass had to be determinedcompile time. Here two related problems must be solved:

The first problem is solved by realizing that noTneeds to bedefined. After all, the intent is to decidecompile-time whether a type isconvertible and not to define aT value or object. Defining objects is nota compile-time but arun-time matter.

By simplydeclaring a function returning aT we cantell the compiler where it should assume aT:

    T makeT();

This mysterious function has the magical power of enticing the compilerinto thinking that aT object comes out of it. However, this functionneeds a small modification before it will actually suit our needs. If, forwhatever reason,T happens to be an array then the compiler will choke onT makeT() as functions cannot return arrays. This, however, is easilysolved, as functionscan returnreferences to arrays. So the abovedeclaration is changed into:

    T const &makeT();

Next we pass aT const & totest:following code:

    test(makeT())

Now that the compiler seestest being called with aT const &argument it decides that its return value isConvertible if aconversion is in fact possible. Otherwise it decides that its return valueisNotConvertible (as the compiler, in that case, selectedtest(...)).

The second problem, distinguishingConvertible fromNotConvertibleis solved exactly the wayisClass could be determined in section23.6.1,viz. by making their sizes different. Having done so thefollowing expression determines whetherT is convertible fromU ornot:

    isConvertible = sizeof(test(makeT())) == sizeof(Convertible);

By usingchar forConvertible andChar2 (cf. section23.6.1) forNotConvertible the distinction can be made.

The above can be summarized in a class templateLconvertibleToR,having two template type parameters:

    template <typename T, typename U>    class LconvertibleToR    {        struct Char2        {            char array[2];        };        static T const &makeT();        static char test(U const &);        static Char2 test(...);        public:            LconvertibleToR(LconvertibleToR const &other) = delete;            enum { yes = sizeof(test(makeT())) == sizeof(char) };            enum { sameType = 0 };    };    template <typename T>    class LconvertibleToR<T, T>    {        public:            LconvertibleToR(LconvertibleToR const &other) = delete;            enum { yes = 1 };            enum { sameType = 1 };    };
As the class template deletes its copy constructor no object can becreated. Only itsenum values can be interrogated. The next example writes1 0 1 0 when run from amain function:
    cout <<        LconvertibleToR<ofstream, ostream>::yes << " " <<        LconvertibleToR<ostream, ofstream>::yes << " " <<        LconvertibleToR<int, double>::yes << " " <<        LconvertibleToR<int, string>::yes <<        "\n";

23.9.3.1: Determining inheritance

Now that it is possible to determine type convertibility, it's easy todetermine whether a typeBase is a (public) base class of a typeDerived.

Inheritance is determined by inspecting convertibility of (const)pointers.Derived const * can be converted toBase const * if

Assuming the last conversion isn't used inheritance can be determinedusing the following trait classLBaseRDerived.LBaseRDerived providesan enumyes which is 1 if the left type is a base class of the right typeand both types are different:
    template <typename Base, typename Derived>    struct LBaseRDerived    {        LBaseRDerived(LBaseRDerived const &) = delete;        enum {            yes =                LconvertibleToR<Derived const *, Base const *>::yes &&                not LconvertibleToR<Base const *, void const *>::sameType        };    };

If code should not consider a class to be its own base class, then thetrait classLBaseRtrulyDerived can be used to perform a strict test. Thistrait class adds a test for type-equality:

    template <typename Base, typename Derived>    struct LBaseRtrulyDerived    {        LBaseRtrulyDerived(LBaseRtrulyDerived const &) = delete;        enum {            yes =                LBaseRDerived<Base, Derived>::yes &&                not LconvertibleToR<Base const *, Derived const *>::sameType        };    };
Example: the next statement displays1: 0, 2: 1, 3: 0, 4: 1, 5: 0when executed from amain function:
    cout << "\n" <<        "1: " << LBaseRDerived<ofstream, ostream>::yes << ",  " <<        "2: " << LBaseRDerived<ostream, ofstream>::yes << ",  " <<        "3: " << LBaseRDerived<void, ofstream>::yes << ",  " <<        "4: " << LBaseRDerived<ostream, ostream>::yes << ",  " <<        "5: " << LBaseRtrulyDerived<ostream, ostream>::yes <<        "\n";

23.10: Template TypeList processing

This section serves two purposes. It illustrates capabilities of the varioustemplate meta-programming techniques, which can be used as a source ofinspiration when developing your own templates; and it offers a concreteexample, illustrating some of the power offered by these techniques.

This section itself was inspired by Andrei Alexandrescu's (2001) bookModern C++ design. It diverts from Alexandrescu's book in its use ofvariadic templates which were not yet available when he wrote his book. Evenso, the algorithms used by Alexandrescu are still useful when using variadictemplates.

C++ offers thetuple to store and retrievevalues of multiple types. Here the focus is merely on processing types. Asimple structTypeList is going to be used as our working horse for theupcoming subsections. Here is its definition:

    template <typename ...Types>    struct TypeList    {        TypeList(TypeList const &) = delete;        enum { size = sizeof ...(Types) };    };
A typelist allows us to store any number of types. Here is an examplestoring the three typeschar, short, int in aTypeList:
    TypeList<char, short, int>

23.10.1: The length of a TypeList

As the number of types in a parameter pack may be obtained using thesizeofoperator (cf. section22.5) it is easy to obtain the number of typesthat were specified with a certainTypeList. For example, the followingstatement displays the value 3:
    std::cout << TypeList<int, char, bool>::size << '\n';

However, it's illustrative to see how the number of types specified with aTypeList could be determined ifsizeof hadn't been available.

To obtain the number of types that were specified with aTypeListthe following algorithm is used:

The algorithm uses recursion to define the length of aTypeList. InexecutableC++ recursion could also be used in comparable situations. Forexample recursion can be used to determine the length of an NTBS:
    size_t c_length(char const *cp)    {        return *cp == 0 ? 0 : 1 + c_length(cp + 1);    }

WhileC++ functions usually use iteration rather than recursion,iteration is not available to template meta programming algorithms. Intemplate meta programming repetitionmust be implemented usingrecursion. Furthermore, whileC++ run-time code may use conditions todecide whether or not to start the next recursion template meta programmingcannot do so. Template meta programming algorithms must resort to (partial)specializations. The specializations are used to select alternatives.

The number of types that are specified in aTypeList can be computedusing the following alternate implementation ofTypeList, using a genericstruct declaration and two specialization for the empty and non-emptyTypeList (cf. the above description of the algorithm):

    template <typename ...Types>    struct TypeList;    template <typename Head, typename ...Tail>    struct TypeList<Head, Tail...>    {        enum { size = 1 + TypeList<Tail...>::size };    };    template <>    struct TypeList<>    {        enum { size = 0 };    };

23.10.2: Searching a TypeList

To determine whether a particular type (calledSearchType below) ispresent in a givenTypeList, an algorithm is used that either defines`index' as -1 (if SearchType is not an element of theTypeList ) or itdefines `index' as the index of the first occurrence of SearchType intheTypeList. The following algorithm is used: The algorithm is implemented using a variadic template structListSearch expecting a parameter pack:
    template <typename ...Types>    struct ListSearch    {        ListSearch(ListSearch const &) = delete;    };
Specializations handle the alternatives mentioned with the algorithm: Here is an example showing howListSearch can be used:
    std::cout <<        ListSearch<char, TypeList<int, char, bool>>::index << "\n" <<        ListSearch<float, TypeList<int, char, bool>>::index << "\n";

23.10.3: Selecting from a TypeList

The inverse operation of determining the index of a certain type in aTypeList is retrieving the type given its index. This inverse operation isthe topic of this section.

The algorithm is implemented using a structTypeAt.TypeAt specifies ausing-declaration to define the type matching a given index. But the indexmight be out of bounds. In that case we have several options:

The first alternative is implemented below. The other alternatives are notdifficult to implement and are left as exercises for the reader. Here's howTypeAt works: Here is howtypeAt can be used. Uncommenting the first variabledefinition causes aTypeAt index out of bounds compilation error:
    using list3 = TypeList<int, char, bool>;//    TypeAt<3, list3>::Type invalid;    TypeAt<0, list3>::Type intVariable = 13;    TypeAt<2, list3>::Type boolVariable = true;    cout << "The size of the first type is " <<                sizeof(TypeAt<0, list3>::Type) << ", "            "the size of the third type is " <<                sizeof(TypeAt<2, list3>::Type) << "\n";    if (typeid(TypeAt<1, list3>::Type) == typeid(char))        cout << "The typelist's 2nd type is char\n";    if (typeid(TypeAt<2, list3>::Type) != typeid(char))        cout << "The typelist's 3nd type is not char\n";

23.10.4: Prefixing/Appending to a TypeList

Prepending or appending a type to aTypeList is easy and doesn't requirerecursive template meta programs. Two variadic template structsAppend andPrefix and two specializations are all it takes.

Here are the declarations of the two variadic template structs:

    template <typename ...Types>    struct Append;    template <typename ...Types>    struct Prefix;

To append or prefix a new type to a typelist, specializations expect atypelist and a type to add. Then, they simply define a newTypeList alsoincluding the new type. TheAppend specialization shows that a templatepack does not have to be used as the first argument when defining anothervariadic template type:

    template <typename NewType, typename ...Types>    struct Append<TypeList<Types...>, NewType>    {        using List = TypeList<Types..., NewType>;    };    template <typename NewType, typename ...Types>    struct Prefix<NewType, TypeList<Types...>>    {        using List = TypeList<NewType, Types...>;    };

23.10.5: Erasing from a TypeList

It is also possible to erase types from aTypeList. Again, there areseveral possibilities, each resulting in a different algorithm. Doubtlessly there are other ways of erasing types from aTypeList. Which ones are eventually implemented depends of course onthe circumstances. As template meta programming is very powerful most if notall algorithms can probably be implemented. As an illustration of how to erasetypes from aTypeList the above-mentioned algorithms are now developed inthe upcoming subsections.

23.10.5.1: Erasing the first occurrence

To erase the first occurrence of a specifiedEraseType from aTypeLista recursive algorithm is used once again. The template meta program uses agenericErase struct and several specializations. The specializationsdefine a typeList containing the resultingTypeList after theerasure. Here is the algorithm: Here is a statement showing howErase can be used:
    cout <<            Erase<int, TypeList<char, double, int>>::List::size << '\n' <<            Erase<char, TypeList<int>>::List::size << '\n' <<            Erase<int, TypeList<int>>::List::size << '\n' <<            Erase<int, TypeList<>>::List::size << "\n";

23.10.5.2: Erasing a type by its index

To erase a type from aTypeList by its index we again use arecursive template meta program.EraseIdx expects asize_t index valueand aTypeList from which itsidxth (0-based) type must beerased.EraseIdx defines the typeList containing the resultingTypeList. Here is the algorithm: Here is a statement showing howEraseIdx can be used:
    if    (        typeid(TypeAt<2,                EraseIdx<1,                   TypeList<int, char, size_t, double, int>>::List                >::Type        )        == typeid(double)    )        cout << "the third type is now a double\n";

23.10.5.3: Erasing all occurrences of a type

Erasing all typesEraseType from aTypeList can easily be accomplishedby applying the erasure procedure not only to the head of theTypeList butalso to theTypeList's tail.

Here is the algorithm, described in a slightly different order thanErase's algorithm:

Here is a statement showing howEraseAll can be used:
    cout <<        "After erasing size_t from "            "TypeList<char, int, size_t, double, size_t>\n"            "it contains " <<                EraseAll<size_t,                         TypeList<char, int, size_t, double, size_t>                >::List::size << " types\n";

23.10.5.4: Erasing duplicates

To remove all duplicates from aTypeList all theTypeList's firstelements must be erased from theTypeList's tail, applying the procedurerecursively to theTypeList's tail. The algorithm, outlined below, merelyexpects aTypeList: Here is an example showing howEraseDup can be used:
    cout <<        "After erasing duplicates from "             "TypeList<double, char, int, size_t, int, double, size_t>\n"        "it contains " <<        EraseDup<            TypeList<double, char, int, size_t, int, double, size_t>        >::List::size << " types\n";

23.11: Using a TypeList

In the previous sections the definition and some of the features of typelistswere discussed. MostC++ programmers consider typelists bothexciting and an intellectual challenge, honing their skills in the area ofrecursive programming.

But there's more to typelist than a mere intellectual challenge. In the finalsections of this chapter the following topics are covered:

Again, much of the material covered by these sections was inspired by Alexandrescu's (2001) book.

23.11.1: The Wrap and Multi class templates

To illustrate template meta programming concepts the template classMultiis now developed. The class templateMulticreates a new class from atemplate template parameterPolicy defining the data storage policy and aseries of types from whichMulti is eventually derived. It does so bypassing its template parameters to its base classMultiBase that in turncreates a final class inheritance tree. Since we don't know how many types aregoing to be usedMulti is defined as a variadic class template using atemplate pack...Types.

In fact, the types that are specified withMulti aren't thatinteresting. They primarily serve to `seed' the classPolicy. Therefore,rather than forwardingMulti's types toMultiBase they are passed toPolicy and the sequence ofPolicy<Type> types is then forwarded toMultiBase.Multi's constructor expects initialization values for itsvariousPolicy<Type>s which are perfectly forwarded toMultiBase.

The classMulti (implementing its constructor in-class to save somespace) shows how a template pack can be wrapped into a policy. Here isMulti's definition:

    template <template <typename> class Policy, typename ...Types>    struct Multi: public MultiBase<0, Policy<Types>...>    {        using PlainTypes = TypeList<Types...>;        using Base = MultiBase<0, Policy<Types>...>;        enum { size = PlainTypes::size };        Multi(Policy<Types> &&...types)        :            MultiBase<0, Policy<Types>...>(                            std::forward<Policy<Types>>(types)...)        {}    };

Unfortunately, the design as described contains some flaws.

There is a way around the problem of duplicate base class types. Ifinstead of inheriting directly from base classes these base classes are firstwrapped in unique type defining classes, then these unique classes can be usedto access the base classes using principles of inheritance. As these uniquetype-defining wrapper classes are merely classes that are derived from the`real' base classes they inherit (and thus: offer) the functionality of theirbase classes. A unique type defining wrapper class can be designed after theclassIntType, defined earlier. The wrapper class we'relooking combines class derivation with the uniqueness offered byIntType. The class templateUWrap has two template parameters: onenon-type parameteridx and one type parameter. By ensuring that eachUWrap definition uses a uniqueidx value unique class types arecreated. These unique class types are then used as base classes of the derivedclassMultiBase:
    template <size_t nr, typename Type>    struct UWrap: public Type    {        UWrap(Type const &type)        :            Type(type)        {}    };
UsingUWrap it's easy to distinguish, e.g., twovector<int>classes:UWrap<0, vector<int>> could refer to the firstvector<int>,UWrap<1, vector<int>> to the second vector.

Uniqueness of the variousUWrap types is assured by the classtemplateMultiBase as discussed in the next section.

It must also be possible to initialize aMulti class object. Itsconstructor therefore expects the initialization values for all itsPolicyvalues. So if aMulti is defined forVector, int, string then itsconstructor can receive the matching initialization values. E.g.,

    Multi<Vector, int, string> mvis({1, 2, 3}, {"one", "two", "three"});

23.11.2: The MultiBase class template

The class templateMultiBase isMulti's base class. Itdefines a class that, eventually, is derived from the list ofPolicytypes that, in turn, were created byMulti using any additional types thatwere passed to it.

MultiBase itself has no concept of aPolicy. ToMultiBase theworld appears to consist of a simple template pack whose types are used todefine a class from. In addition to thePolicyTypes template pack,MultiBase also defines asize_t nr non-type parameter that is used tocreate uniqueUWrap types. Here isMultiBase's generic classdeclaration:

    template <size_t nr, typename ...PolicyTypes>    struct MultiBase;

Two specializations handle all possibleMultiBase invocations. Onespecialization is a recursive template. This template handles the first typeofMultiBase's template parameter pack and recursively uses itself tohandle the remaining types. The second specialization is invoked once thetemplate parameter pack is exhausted and does nothing. Here is the definitionof the latter specialization:

    template <size_t nr>    struct MultiBase<nr>    {};

The recursively defined specialization is the interesting one. It performsthe following tasks:

An illustration showing the layout of theMultiBase classhierarchy is provided in figure30.

Figure 30: Layout of a MultiBase class hierarchy

MultiBase's constructor simply receives the initialization values thatwere (originally) passed to theMulti object. Perfect forwarding is usedto accomplish this.MultiBase's constructor passes its first parametervalue to itsUWrap base class, also using perfect forwarding.MultiBase's recursive definition is:

    template <size_t nr, typename PolicyT1, typename ...PolicyTypes>    struct MultiBase<nr, PolicyT1, PolicyTypes...> :                                public UWrap<nr, PolicyT1>,                                public MultiBase<nr + 1, PolicyTypes...>    {        using Type = PolicyT1;        using Base = MultiBase<nr + 1, PolicyTypes...>;        MultiBase(PolicyT1 && policyt1, PolicyTypes &&...policytypes)        :            UWrap<nr, PolicyT1>(std::forward<PolicyT1>(policyt1)),            MultiBase<nr + 1, PolicyTypes...>(                              std::forward<PolicyTypes>(policytypes)...)        {}    };

23.11.3: Support templates

TheMulti class template definesPlainTypes as theTypeListholding all the types of its parameter pack. EachMultiBase derived from aUWrap type also defines a typeType representing the policy type thatwas used to define theUWrap type and a typeBase representing thetype of its nestedMultiBase class.

These three type definitions allow us to access the types from which theMulti object was created as well as the values of those types.

The class templatetypeAt, is a pure template meta program class template(it has no run-time executable code). It expects asize_t idx templateargument specifying the index of the policy type in aMulti type object aswell as aMulti class type. It defines the typeType as theTypedefined byMulti'sMultiBase<idx, ...> base class. Example:

    typeAt<0, Multi<Vector, int, double>>::Type // Type is vector<int>

The class templatetypeAt defines (and uses) a nested class templatePolType doing all the work.PolType's generic definition specifies twotemplate parameters: an index used to specify the index of the requested typeand a typename initialized by aMultiBase type argument.PolType'srecursive definition recursively reduces its index non-type parameter,passing the next base class inMultiBase's inheritance tree to therecursive call. AsPolType eventually defines the typeType to be therequested policy type the recursive definition defines itsType as thetype defined by the recursive call. The final (non-recursive) specializationdefines the initial policy type of theMultiBase type asType. Here istypeAt's definition:

    template <size_t index, typename Multi>    class typeAt    {        template <size_t idx, typename MultiBase>        struct PolType;        template <size_t idx,                  size_t nr, typename PolicyT1, typename ...PolicyTypes>        struct PolType<idx, MultiBase<nr, PolicyT1, PolicyTypes...>>        {            using Type = typename PolType< idx - 1, MultiBase<nr + 1,                                           PolicyTypes...> >::Type;        };        template <size_t nr, typename PolicyT1, typename ...PolicyTypes>        struct PolType<0, MultiBase<nr, PolicyT1, PolicyTypes...>>        {            using Type = PolicyT1;        };    public:        typeAt(typeAt const &) = delete;        using Type = typename PolType<index, typename Multi::Base>::Type;    };

The types specified byMulti's parameter pack can also be retrieved usinga second helper class template:plainTypeAt. Example:

    plainTypeAt<0, Multi<Vector, int, double>>::Type // Type is int

The class templateplainTypeAt uses a comparable (but simpler)implementation thantypeAt. It is also a pure template meta program classtemplate defining a nested class templateAt.At is implemented liketypeAt but it visits the types of the original template pack that waspassed toMulti, and made available byMulti as itsPlainTypestype. Here isplainTypeAt's definition:

    template <size_t index, typename Multi>    class plainTypeAt    {        template <size_t idx, typename List>        struct At;        template <size_t idx, typename Head, typename ...Tail>        struct At<idx, TypeList<Head, Tail...>>        {            using Type = typename At<idx - 1, TypeList<Tail...>>::Type;        };        template <typename Head, typename ...Tail>        struct At<0, TypeList<Head, Tail...>>        {            using Type = Head;        };    public:        plainTypeAt(plainTypeAt const &) = delete;        using Type = typename At<index, typename Multi::PlainTypes>::Type;    };

Arguably the neatest support template isget. This is a function templatedefiningsize_t idx as its first template parameter andtypename Multias its second template parameter. The function templateget defines onefunction parameter: a reference to aMulti, so it can deduceMulti'stype by itself. Knowing that it's aMulti, we reason that it is also aUWrap<nr, PolicyType> and therefore also aPolicyType, as the latterclass is defined as a base class ofUWrap.

Since class type objects can initialize references to their base classes thePolicyType & can be initialized by an appropriateUWrap reference,which in turn can be initialized by aMulti object. Since we candeterminePolicyType usingTypeAt (note that evaluatingtypenametypeAt<idx, Multi>::Type is a purely compile-time matter), thegetfunction can very well be implementedinline by a singlereturnstatement:

    template <size_t idx, typename Multi>    inline typename typeAt<idx, Multi>::Type &get(Multi &multi)    {        return static_cast<                UWrap<idx, typename typeAt<idx, Multi>::Type> &>(multi);    }
The intermediateUWrap cast is required to disambiguate betweenidentical policy types (like twovector<int> types). AsUWrap isuniquely determined by itsnr template argument and this is the numberargument that is passed toget ambiguities can easily be prevented.

23.11.4: Using Multi

Now thatMulti and its support templates have been developed, how can aMulti be used?

A word of warning is in place. To reduce the size of the developed classesthey were designed in a minimalist way. For example, theget functiontemplate cannot be used withMulti const objects and there is no default,or move constructor available forMulti types.Multi was designed toillustrate some of the possibilities of template meta programming andhopefullyMulti's implementation served that purpose well. But can it beused? If so, how?

This section provides some annotated examples. They may be concatenated todefine a series of statements that could be placed in amain function'sbody, which would result in a working program.

23.12: Expression Templates

Assume we are processingstd::vector objects. Vectors may be assigned toeach other, but that's about it. We've seen (cf. section12.3.2) that itsmember functions tend to operate on the current vector, but arithmeticoperations like addition, subtraction, multiplication and the like cannotbe applied to pairs of vectors.

Implementing the, e.g., addition operator for vectors is not difficult. IfVecType is our vector type, then implementing free functions likeVecType &&operator+(VecType const &lhs, VecType const &rhs) andVecType &&operator+(VecType &&lhs, VecType const &rhs) performing the additions is a simple exercise (cf. chapter11).

Now consider an expression likeone + two + three + four. It takes foursteps to compute this sum: first,tmp = one is computed, creating theeventual return value. The vectortmp becomes the eventual returnvalue. Once it is availabletmp += two is computed, followed bytmp +=three, and finally bytmp += four (of coursewe shouldn't implementstd::vector::operator+= as the std namespace isoff-limits to us, and we shouldn't derive a class fromstd::vectorofferingoperator+= according to Liskov's Substitution Principle(cf. section14.7), but we could get around that. Here we simply assumeoperator+= is available).

Here's how we might implementoperator+= forVecType:

    VecType &VecType::operator+=(VecType const &rhs)    {        for (size_t idx = 0, end = size(); idx != end; ++idx)            (*this)[idx] += rhs[idx];        return *this;    }

Consider this implementation: once we addVecType objects and suchobjects haveN elements then we have to perform2 * N indexevaluations. When addingk VecType objects this adds up to2 * N * kindex expression evaluations (as eventually we also have to assign theelements of the resulting temporary object to the destination object): lotsof index expression evaluations.

If instead we could manage to perform the evaluations `row by row', wewould only have to access each vector element only once (which in particularapplies to the temporary object). In that case, when adding k objects,assigning the sums of their respective elements to a destination vector wehave to computeN * (k + 1) index expressions (`k' for each of thevectors,`1' for the destination vector).

Fork == 1 the two methods are equally efficient in terms of indexcomputations. But that's not addition, that is assignment. So when adding anynumber of vectors, assigning their sum to a destination vector using expressiontemplates is more efficient than the ordinary implementation of the additionoperator. We'll have a look at the design and implementation of expressiontemplates in the coming sections.

23.12.1: Designing an Expression Template

As we've seen, when using a standard implementation of an expression likeone + two + three + four, where the objects are vectors havingnelements, then if we havek vectors we have to perform a total ofk * 2* n index evaluations.

Expression templates allow us to avoid many of these evaluations. When usingexpression templates these templates may access the vectors, but theirelements are not accessed during addition operations.

Assuming our expression template is named ET, and we want to addone + two+ three, then the first+ operator merely createsET(one, two). Notethat no addition is actually performed,ET merely stores (constant)references toone (becomingET's lhs data member) andtwo(becomingET's rhs data member). In general,ET stores references tothe two arguments that are passed to its constructor.

At the next addition operator anotherET is created. Its constructorarguments are, respectively, theET object that has just been constructedforone andtwo, and the vectorthree. Again, no addition isperformed by the ET objects.

This algorithm easily generalizes to any number of vectors. Parentheses canalso be used. E.g.,(one + two) + (three + four) results in

    ET(ET(one, two), ET(three, four))

Presumably, at some point we want to obtain the sum of the vectors. For thisthe expression template is provided with a conversion operator, converting theET object to a vector, or maybe an assignment operator doing the same.

The conversion operator looks like this:

    operator ET::VecType() const    {                                                                VecType retVal;        retVal.reserve(size());        for (size_t ix = 0, end = size(); ix != end; ++ix)            new(&retVal[ix]) value_type((*this)[ix]);                                                             return retVal;    }

Placement new is used for efficiency reasons: there's no need toinitializeretVal with default values first. The really interesting part,however, is hidden behind the(*this)[idx] expression: at this point thereal addition takes place.

ET's index operator simply adds the values returned by thecorresponding index expressions of itslhs andrhs data members. If adata member refers to a vector then the corresponding vector element is used,adding it to the other data member's value. If a data member itself refers toan ET object, then that nestedET object's index operator performs thesame addition on its own data members, returning their sum. So, an expressionlike(*this)[0] returnsfirst[0] + second[0] + third[0], and thecomputed sum is then stored inretVal[0] using placement new.

In this case the required number of index expression evaluations aren* k (for the n elements of the k vectors) plus n (for the n elements ofretVal, adding up to(k + 1) * n).

Since(k + 1) * n < 2 * k * n fork > 1 expression templates evaluatethe requested addition more efficiently than the traditional implementation ofoperator+. An additional benefit of using expression templates is thatthey do not create additional temporary vector objects when parenthesizedexpressions are used.

23.12.2: Implementing an Expression Template

In this section we specifyusing IntVect = std::vector<int> to illustratethe construction of an expression template.

Starting point is a simplemain function, in which severalIntVectobjects are added. E.g.,

    int main()    {        IntVect one;        IntVect two;        IntVect three;        IntVect four;                // ... assume the IntVects somehow receive values        four = one + two + three + four;    }

At this point the code does not suggest that expression templates aregoing to be used. However,operator+'s implementation is special: it's atemplate merely returning an object constructed byoperator+:

    template<typename LHS, typename RHS>    BinExpr<LHS, RHS, plus> operator+(LHS const &lhs, RHS const &rhs)    {        return BinExpr<LHS, RHS, plus>{ lhs, rhs };    }

Our expression template is calledBinExpr. It has three template typeparameters: two object types and a template template parameter performing therequested operation. Its declaration looks like this:

    template<typename LHS, typename RHS, template<typename> class Operation>    struct BinExpr;

SinceLHS andRHS can either be the data type that is processed bythe expression template, or aBinExpr two different typenames arerequired.Operation is the operation that is performed by the expressiontemplate. By using a template template parameter we can useBinExpr toperform any operation we want, not just addition. Predefined functiontemplates likestd::plus can be used for the standard arithmeticoperators; for other operators we can define our own function templates.

BinExpr's constructor initializes constant references tolhs andrhs. Its in-class implementation is

    BinExpr(LHS const &lhs, RHS const &rhs)    :        d_lhs(lhs),        d_rhs(rhs)    {}

To retrieve the resultingIntVect a conversion operator is defined. Wealready encountered its implementation (in the previous section). Here is it,as an in-class implementedBinExpr member:

    operator ObjType() const    {                                                                ObjType retVal;        retVal.reserve(size());        for (size_t idx = 0, end = size(); idx != end; ++idx)            new(&retVal[idx]) value_type((*this)[idx]);                                                             return retVal;    }

We return to the typeObjType below. At this point it can beconsidered anIntVect. The membersize() simply returnsd_lhs.size(): in any sequence ofIntVect additionsLHS eventuallyis anIntVect, and so everyBinExpr defines a validsize() likeso:

    size_t size() const    {        return d_lhs.size();    }

The only remaining member to implement isoperator[]. Since itreceives an index, it only needs to perform the requested operation on thecorresponding index elements of itsd_lhs andd_rhs data members. Thebeauty of expression templates is that if either one itself is aBinExprthat expression template in turn calls itsoperator[], eventuallyperforming the requested operation on all corresponding elements of allIntVect objects. Here is its implementation:

    value_type operator[](size_t ix) const    {        static Operation<value_type> operation;        return operation(d_lhs[ix], d_rhs[ix]);    }

This implementation uses another type:value_type which is the type of the elements of the vector type that is processed by the expressiontemplate. LikeObjType before, its definition is covered below. The staticdata memberoperation simply is an instantiation of theOperation typethat is specified when constructing anExprType object.

In the next section we take a closer look atObjType andvalue_type.

23.12.3: The BasicType trait class and ordering classes

TheBinExpr expression template needs to be aware of two types before itcan instantiate objects. First,ObjType must be known, as this is the type of object that is handled by the expression template.ObjType objectscontain values, and we require that the type of these values can be determinedasObjType::value_type. E.g., for ourIntVect data typevalue_typeisint.

In expressions likeone + two + three, theBinExpr expression templatereceives twoIntVect objects. This is always true: theBinExpr that isfirst constructed receives twoIntVect objects. In this caseObjTypeis simplyLHS, andObjType::value_type is also available: eithervalue_type is already defined byLHS orBinExprrequires thatit defines typevalue_type.

Since arguments toBinExpr objects are notalways of the basicObjType type (BinExpr objects at the next nesting level receive atleast oneBinExpr argument) we need a way to determineObjType from aBinExpr. For this we use atrait class. The trait classBasicType receives a typename template argument, and equatesits typeObjType to the received template type argument:

    template<typename Type>    struct BasicType    {        using ObjType = Type ;    };

A specialization handles the case whereType in fact is aBinExpr: template<typename LHS, typename RHS, template<typename> class Operation>

    template<typename LHS, typename RHS, template<typename> class Operation>    struct BasicType<BinExpr<LHS, RHS, Operation>>    {        using ObjType = BinExpr<LHS, RHS, Operation>::ObjType ;    };

SinceBinExprrequires thatObjType::value_type is a definedtype,value_type has automatically been taken care of.

AsBinExpr refers toBasicType andBasicType refers toBinExpr somewhere we must provide a forward declaration. AsBinExpr'sdeclaration has already been provided, we start with that declaration,resulting in:

    BinExpr's declaration    BasicType's definition    BasicType's specialization (for BinExpr)    template<typename LHS, typename RHS, template<typename> class Operation>    class BinExpr     {        LHS const &d_lhs;        RHS const &d_rhs;        public:            using DataType = BasicType<RHS>::DataType ;            using type = DataType::value_type value_;            // all BinExpr member functions    };

23.13: Concepts

C++ is a strongly typed language: a functionadd(int lhs, int rhs)doesn't acceptstd::string arguments, even though the actual operations(lhs + rhs) are identical forints andstrings.

Templates were introduced so we could designrecipes for the compiler,allowing it to construct type-safe overloaded versions of functions andclasses while keeping their type-safety.

A basic addition function template adding two values looks like this:

    template <typename Type>    Type add(Type const &lhs, Type const &rhs)    {        return lhs + rhs;    }

When this function template is called with arguments of types that do notsupportoperator+ then the compiler notices this, and it will generate anerror. E.g., when calling

    add(std::cerr, std::cout);
theg++ compiler produces some 140 lines of error messages. It noticesthat there's nooperator+ forstd::ostream objects, and then tells uswhat else we might have done (like adding twoints), and where theconstruction of theadd function that should acceptstd::ostreamarguments went wrong. In fact, 140 lines of error messages is rather benign.Getting several hundreds of lines is quite common, and sometimes thelocation of the error isn't mentioned at the top but somewhere near the end ofthe error message output.

TheC++26 standard introducedconcepts allowing us to specifyrequirements for template types. When applying an appropriate concept tothe definition of theadd function template the compiler immediatelypinpoints the error, telling us where and why the error occurred in some 15instead of 140 lines of error messages.

The reduction of the number of lines of error messages by itself is aboon. But the fact that concepts allow us to consciously develop ourtemplates, realizing what the precise requirements are for their use, is atleast as important: it improves the template's documentation, and thus ourunderstanding of templates.

Concepts may be considered the template's answer to the philosphy that liesbehind a strongly typed language. By applying concepts to templates we canspecify type-requirements rather than using the traditional `shotgunempiricism' approach where templates are bluntly used, knowing that thecompiler will complain if things are incorrect. In that sense concepts providetype definitions of types. Concepts have names and can (among other) be usedin template headers where the concept names replace the traditionaltypename keywords.

As an opening illustration, assume that a conceptAddable existsspecifying thatoperator+ must have been defined for the template'stype. The above function templateadd can now be formulated as:

    template<Addable Type>    Type add(Type const &lhs, Type const &rhs)    {        return lhs + rhs;    }

From now on every type that is actually passed toadd must be satisfy theAddable requirements. Here are two expressionsusingadd:

    add("first"s, "second"s);               // (1)    add(map<int, int>{}, map<int, int>{});  // (2)

Expression (1) flawlessly compiles asstring objects can be added;expression (2) fails with the compiler reporting something like

    error: use of function `Type add(const Type&, const Type&)    [with Type = std::unordered_map<int, int>]' with unsatisfied constraints    add(unordered_map<int, int>{}, unordered_map<int, int>{});    note: constraints not satisfied    Type add(const Type&, const Type&)     ...    note: the required expression `(lh + rh)' is invalid

The error message's final `note' clearly states the cause of the problem:you can't add maps.

The difference between the compiler's report using concepts and not usingconcepts again is impressive. When using the traditionaltypename Typespecification in the template header the compiler produces some 17 kB of errormessages, spread out over more than 200 lines.

In the following sections we cover how concepts are defined, what kind ofrequirements can be formulated, and how they can be used in practice.

23.13.1: Defining concepts

As a prelude before actually looking at how concepts are defined it is notedthat concept names, like class names, type names, function names, and variablenames should suggest their purposes. Don't name a concept `Constraint' or`Concept', but use names like `Addable' and `HasValueType'.

Concepts are templates. They start with a template header (thetemplate headers shown in the examples define a single template typeparameter, but multiple template parameters are also used).

In the previous section we used the conceptAddable. Here is how it canbe defined:

    template <typename Type>    concept Addable =        requires(Type lh, Type rh)        {            lh + rh;        };

The concept's template header is followed by the keywordconcept, theconcept's name, and the assignment operator. Following the assignment operatorrequirement specifications are provided.

Semicolons end concept definitions. This concept uses asimple requirement (cf. section23.13.2.1) indicating thatoperator+ must have been defined forAddable templates' types.

Requirements come in many forms. A very simple form consists of just abool value, which is sometimes useful when developing a concept. Such aconcept looks like this:

template <typename Type>    concept IsTrue =         true;
But in most situationsrequires specifications are used. They resemblefunction definitions having parameter lists optionally defining variables ofthe types that specified in the concept's template header and compoundstatements specifying requirements.

Concepts arenever instantiated. They are used compile-time to verify thattemplate types satisfy the imposed requirements. Thus there's no needto use refererences in parameter lists ofrequires specifications. TheconceptAddable simply uses

    requires(Type lh, Type rh)

and there's no need to specify

    requires(Type const &lh, Type const &rh)
(That is, usually there is no need for this. In section23.13.2.4 weencounter a situation where a more specific parameter definition might beappropriate.)

Here are two examples of templates usingconcept Addable. The firstexample usesAddable instead oftypename when specifying the templateheader, the second example appends the concept specification to the templateheader itself:

    template<Addable Type>    Type add2(Type const &x, Type const &y)    {        return x + y;    }        template<typename Type>        requires Addable<Type>    Type add(Type const &x, Type const &y)    {        return x + y;    }

Template declarations using concepts are specified accordingly. Simply replacethe function template's body by a semicolon.

Concepts may also be defined by extending or combining existing concepts.Nesting concepts is covered in section23.13.2.4.

Although concepts are templates, they cannot be specialized. If a conceptshould recognize specializations then these specializations must be handled bythe concepts' definitions. See section23.13.2.3 for an illustration.

23.13.2: Requirements

The bodies ofrequires declarations contain define constraints to apply totemplate parameters. There are four types of requirements:

Constraints must be compile-time verifiable.

When multiple constraints are specified, they mustall be compile-timeverifiable, and an actual type is only accepted by the compiler if allrequirements could be satisfied.

23.13.2.1: Simple requirements

We've already encountered various examples ofsimple requirements: theyspecify the operations that must be supported by the variable(s) declared inthe parameter lists ofrequires specifications. When the requirementsrefer to single variables singleType parameters suffice; when requirements involve different types then the concept's template head declaresthose different types and therequires parameter list will usually definevariables of those different types. Theconcept BasicMath specifies twotypes, and uses four simple requirements to specify the four basic arithmeticoperations:
    template <typename LhsType, typename RhsType>    concept BasicMath =        requires(LhsType lhs, RhsType rhs)        {            lhs + rhs;      // addition must be available            lhs - rhs;      // subtraction must be available            lhs * rhs;      // multiplication must be available            lhs / rhs;      // division must be available        };

Specifying constraints does not necessarily mean that the constraints asspecified literally apply to run-time situations. To require the existence ofthe index operator the following simple requirement can be used:

    template <typename Type>    concept HasIndex =        requires(Type tp)        {            tp[0];        };        template <HasIndex Type>    auto idx(Type const &obj, size_t idx)    {        return obj[idx];    }

Here thestand-in argument 0 is used to specify the index operator'sargument. The argument value used in the simple requirement really is astand-in. The following code fragment compiles, asstring supports theindex operator. Although argument 0 is used in the simple requirementspecification the argument 5 is in fact being used:

    string str;    idx(str, 5);

Other thanint index types can be specified analogously. Here is anexample showing how to define and use a conceptHasStrIndex requiring theavailability ofstd::string arguments of index operators:

    template <typename Type>    concept HasStrIndex =        requires(Type tp)        {            tp[std::string{}];        };        template <HasStrIndex Type>    auto value(Type &obj, std::string const &key)    {        return obj[key];    }        int main()    {        std::map<std::string, double> msd;        value(msd, "hi");    }

23.13.2.2: Type requirements

Sometimes templates must use sub-types of template types. Examples aretemplates assuming the existence ofiterator (likevector<int>::iterator) orvalue_type which is used when callingstd::push_back.

To specify that a sub-type must be available the concept'srequiresspecification needs no parameters, but can directly refer to the subtype. It'salso possible to combine type requirements with requirements thatdodefine a non-empty parameter list, and so therequires's parameter listdoes nothave to be empty. Here is a concept that specifies a plain typerequirement:

    template <typename Type>     concept HasValueType =        requires()        {            typename Type::value_type;        };
and here's a concept that combines a simple requirement with a typerequirement:
    template <typename Type>    concept Iteratable =        requires(Type tp)        {            typename Type::iterator;            tp.begin();        };        template <Iteratable Type>    auto iter(Type const &obj)    {        return obj.begin();    }

Callingiter with astring argument succeeds, calling it with aqueue argument results in two error notes: noType::iterator and notp.begin().

Types specified in type requirements don't necessarily have to refer totypes. They may also specify be the names of nested classes orenumerations defined byType. When specifying enumerations they do nothave to be strongly typed.

23.13.2.3: Compound requirements

When return types of operations must satisfy certain requirements thencompound requirements should be used. Compound requirements define typeconstraints on expressions embedded in compound statements. TheC++26 standarddefines several concepts that can be used to specify such requirements (seealso section23.13.3 below). Here is an example:
    template <typename Type, typename ReturnType>     concept Return =        requires(Type par)        {                          // par[..] must return a `ReturnType'            { par[0] } -> std::same_as<ReturnType>;         };

This concept can now be used to specify requirements of template typeparameters. E.g.,

    template <typename Type, typename RetType>         requires Return<Type, RetType>    RetType fun(Type tp)    {        return tp[0];    }

Here arguments passed tofun must satify two requirements:

You may have noticed that thestd::same_as concept receives only onetemplate type argument, which (as if by magic) compares it with the typereturned by thepar[0] expression. When peeking at the available conceptsin section23.13.3 you will see that several of those concepts infact define two template type parameters. When these concepts are used incompound requirements then the compiler passes the deduced type of theexpression in the concept's compound statement (so that's the type ofpar[0] in the above example) to the concept's first type, and passes theexplicitly specified type to the concept's second type.

Knowing this we can define our own concepts to use in compoundexpressions. We may define our ownsame_as concept as follows, using aseparate class templateSameTypes.SameTypes defines abool value `value' which is used to decide about the concept'srequirement. The class templateSameTypes uses a specialization to handlethe situation where both types are equal. Note that concepts themselves cannot be specialized:

    template <typename Lhs, typename Rhs>    struct SameTypes            // generic: any two types    {        static bool const value = false;    };    template <typename Lhs>    struct SameTypes<Lhs, Lhs>  //specialization: equal types    {        static bool const value = true;    };    template<typename Compound, typename Specified>    concept Same = SameTypes::value;

Now the conceptSame can be used instead ofstd::same_as by merelyspecifying the required type:

    template <typename Type, typename ReturnType>     concept Return =        requires(Type par)        {                          // par[..] must return a `ReturnType'            { par[0] } -> Same<ReturnType>;         };
Although in this case it isn't important which actual type is used asargument for which concept type parameter, the compiler specifies the compoundexpression's type as template argument forSame's Compound parameterwhereasReturnType is used as template argument forSame's Specifiedparameter.

Multiple type requirements can be specified by providing multiple compoundrequirements as in the following example:

    template <typename Type>     concept MultiArgs =        requires(Type lhs, Type rhs)        {            { lhs + rhs   } -> std::same_as<Type>;              { lhs += rhs  } -> std::same_as<Type &>;            { lhs.c_str() } -> std::same_as<char const *>;        };

If it is required that the compound operation doesn't throw exceptions thennoexcept can be written immediately following the compound requirement'slate return type arrow (->). Thenoexcept specification itself maythen optionally be followed by a type constraint.

Finally, the late return type specifications itself is optional, in which casethe compound requirement acts like a simple requirement: it requires theexistence of the expression that's specified in the compound statement. Inthis case: don't forget to add the semicolon following the closing parenthesisof the compound requirement:

    template <typename Type>     concept Increment =        requires(Type par)        {            { ++par };              // same as:            ++par;        };

23.13.2.4: Nested requirements

Concepts can be nested. Being able to nest concepts is very useful as itallows us to hierarchically order concepts and to define concepts in terms ofexisting concepts.

In chapter18 iterators were introduced (section18.2). Commonly five conceptually different iterator types aredistinguished:

Figure 31: Concept Hierarchy

All iterator types support (in)equality checks and increment operators. Thus,at the basis of all iterators we find the requirements that iterators must becomparable and incrementable. Concepts covering those requirements are easilyconstructed (see also figure31):

    template <typename Type>    concept Comparable =        requires (Type lhs, Type rhs)        {            lhs == rhs;            lhs != rhs;        };        template <typename Type>    concept Incrementable =        requires (Type type)        {            ++type;            type++;        };

Note that no type is specified following thelhs == rhs andlhs !=rhs requirements, as those types are implied by their operators.

Two more concepts are defined: one allowing dereferencing pointersreturning constant references and one returning modifiable references. Toallow the compiler to verify those requirements we also implicitly require the(commonly encountered) existence of typenameType::value_type:

    template <typename Type>    concept Dereferenceable =        requires(Type type)        {            { *type } -> std::same_as<typename Type::value_type &>;        };

    template <typename Type>    concept ConstDereferenceable =        requires(Type type)        {            { *type } -> std::same_as<typename Type::value_type const &>;        };

Not much of a hierarchy so far, but that changes now that we're about todefine concepts for iterators.

An input iterator is an iterator that is comparable, incrementable andconst-dereferenceable. For each of these requirements concepts were definedwhich can be combined using boolean operators when defining the conceptInIterator. Note that template type parameters of conceptsmust usethetypename keyword. Concepts' template parameters cannot be constrained byspecifying them in terms of existing concepts (whichis possible whendefining function and class templates).

Here is the definition of the conceptInIterator. The function templateinFun (below the conceptInIterator) illustrates how aconstrained template parameter type can be specified in template headers:

    template <typename Type>    concept InIterator =        Comparable<Type> and Incrementable<Type> and        ConstDereferenceable<Type>;        template <InIterator Type>    void inFun(Type tp)    {}

The concept for output iterators (and its use, as in the functiontemplateoutFun) is defined analogously. This time requiringdereferenceable types rather than const-dereferenceable types:

    template <typename Type>    concept OutIterator =        Comparable<Type> and Incrementable<Type> and        Dereferenceable<Type>;        template <OutIterator Type>    void outFun(Type tp)    {}

For forward iterators the conceptFwdIterator is defined. A forwarditerator combines the characteristics of input and output iterators, and wemay want to define a forward iterator by requiring the requirements of theInIterator andOutIterator concepts.

However, there's a slight problem. The following class (struct) defines constand non-const dereference operators and may be therefore be passed tofunctions expecting input or output iterators:

    struct Iterable    {        using value_type = int;            Iterable &operator++();        Iterable operator++(int);            int const &operator*() const;        int &operator*();    };        bool operator==(Iterable const &lh, Iterable const &rh);    bool operator!=(Iterable const &lh, Iterable const &rh);    int  operator-(Iterable const &lh, Iterable const &rh);

But when a function template requiresConstDerefenceable arguments thenthe compiler notices that the overloaded memberint &operator*() doesn'treturn anint const &. Even thoughint const &operator*() const isavailable compilation fails. This problem can be solved in two ways: notingthat anint & can be converted to anint const & the predefinedconceptstd::convertible_to instead ofstd::same_as can be used inConstDereferenceable; alternatively itsrequires clause can specifyType const &type instead of justType type. Here is a definition ofConstDereferenceable that, when defining the conceptFwdIter, can beused in combination withDereferenceable:

    template <typename Type>    concept ConstDereferenceable =        requires(Type const &type)        {            { *type } -> std::same_as<typename Type::value_type const &>;        };

The final two iterator types pose no problems: the conceptBiIteratorrequires the constraints of the conceptFwdIterator as well as decrementoperators, and finally the conceptRndIterator requires the constraints ofBiIterator and in addition iterator increments decrements for any stepsize as well as the possibility to subtract iterators:

    template <typename Type>    concept BiIterator =        FwdIterator<Type> and        requires(Type type)        {            --type;            type--;        };        template <typename Type>    concept RndIterator =        BiIterator<Type>        and        requires(Type lhs, Type rhs)        {            lhs += 0;            lhs -= 0;            lhs + 0;            lhs - 0;            { lhs - rhs } -> std::same_as<int>;        };

23.13.3: Predefined concepts

In the previous section we covered defining concept requirements. Whenspecifying some of the requirements already available concepts were used, likestd::same_as. TheC++26 standard provides some 30 predefined concepts whichmay be used to specify type requirements, to specify conversion requirements,and to specify more advanced requirements, sometimes accepting variadictemplate parameters. The currently predefined concepts are covered in thefollowing subsections.

23.13.3.1: Concepts specifying one template type parameter

The following concepts specify just one template type parameter. Their genericform is
    template <typename Type>    concept Name =         ... requirements ...        ;
When used in compound requirements only their names have to bespecified. For example (using the conceptstd::boolean (see below)), torequire that a functionfun receiving an argument of some typeTypereturns a boolean value, the following concept could be defined:
template<typename Type>    concept BoolFun =         requires(Type param)        {            { fun(param) } -> std::boolean;        };

23.13.3.2: Concepts specifying two template type parameters

The following concepts define two template type parameters. Their genericform is
    template <typename LHS, typename RHS>    concept Name =         ... requirements ...        ;
When used in compound requirements the compiler deduces the type of thecompound expression, and then uses that type as LHS. In the type requirementfollowing the compound statement only the RHS type is specified. For example(using the conceptstd::same_as (see below)), to require that a functionfun, receiving an argument of some typeType, returns astd::string the following concept can be defined:
template<typename Type>    concept StringFun =         requires(Type param)        {            { fun(param) } -> std::same_as<std::string>;        };

23.13.3.3: Concepts specifying multiple template type parameters

Most predefined concepts expecting more than two parameters are variadic(cf. section23.13.4).

23.13.4: Applying concepts to template parameter packs

As we have seen (cf. section23.13.3.3) concepts may process template parameter packs. Such concepts are calledvariadic concepts. When definingconcept-protected variadic function or class templates variadic conceptsaren't always required. Consider the following function:
    template <HasSize ...Types>    void fun(Types &&...obj)    {        sum(std::forward<Types &&>(obj)...);    }

Here we see a variadic template, but it defines all its parameters asconstrained types by simply mentioning the conceptHasSize instead of justtypename. TheHasSize concept is very basic: it merely requires thattype.size() exists, returning asize_t:

    template <typename Types>    concept HasSize =        requires (Types type)        {            { type.size() } -> std::same_as<size_t>;        };

Oncefun has verified that all its argument types satisfy theHasSizerequirements no additional checks are necessary. Thefun functiontemplate merely forwards its arguments tosum, a variadic template, thatsimply adds the return values of thesize() members of its arguments:

    size_t sum()    {        return 0;    }        template <typename First, typename  ...Types>    size_t sum(First &&first, Types &&...types)    {        return first.size() + sum(std::forward<Types>(types)...);    }

The wrapper functionfun isn't really required. The variadic templatefunction summing the varioussize() values itself can also be defined sothat its types themselves must satisfy theHasSize concept. Here is thedefinition of the variadic function templatesum2 requiring preciselythat:

    size_t sum2()    {        return 0;    }        template <HasSize First, HasSize  ...Types>    size_t sum2(First &&first, Types &&...types)    {        return first.size() + sum2(std::forward<Types>(types)...);    }

And here is amain function callingfun andsum2:

    int main()    {        fun(queue<int>{}, vector<int>{}, string{});        cout << sum2(queue<int>{}, vector<int>{}, string{}) << '\n';    }

On the other hand, the predefined conceptstd::constructible_fromis avariadic concept, as it accepts a LHS template parameter and a RHS parameterpack. This concept is satisfied if the LHS parameter can be constructedfrom the types specified in its RHS parameter pack. After includingtype_trait defining and using such a concept is not very hard:

    template <typename Class, typename ...Params>    concept Constructible = std::is_constructible<Class, Params ...>::value;        template <typename Class, typename ...Params>        requires Constructible<Class, Params ...>    void fun(Class &&type, Params &&...params)    {}

The recipe for writing variadic concepts is not very complex:

To use the variadic concept in a function or class template its templateparameters are simply forwarded to the concept (as shown in the aboveexample).

When no predefined variadic type trait is available the variadic concept mustuse other means to determine whether its constraints are satisfied or not. Inthose cases define your own variadic type traits. For illustration let'sassume we are looking for a variadic concept that can be used to verify thatthe types of all the arguments that are passed to a variadic function templateare integral types. In this case there is no predefined type trait we can use,so we have to define it ourselves. We define the conceptIntegralOnly as avariadic concept using our self-defined type traitallIntegralTypes, andthereupon use it when defining a function requiring that all of its argumentsare integral values:

    template <typename ...Types>    concept IntegralOnly = allIntegralTypes<Types ...>::value;        template <IntegralOnly ...Types>    void fun(Types ...types)    {}

The generic type traitallIntegralTypes merely specifies that it acceptsany number of type parameters and uses specializations to handle specificcases. One specific case is the case were no types are specified which simplydefines atrue static bool const value:

    template <typename ...Types>    struct allIntegralTypes;        template <>    struct allIntegralTypes<>    {        static bool const value = true;    };

The type trait's partial specialization does the hard work: it determineswhether the first type is integral and combines that (usingand) with thevalue made available by thestruct allIntegralType receiving theremaining types:

    template <typename First, typename ...Types>    struct allIntegralTypes<First, Types ...>    {        static bool const value = std::is_integral<First>::value and                                  allIntegralTypes<Types ...>::value;    };

The functionfun can now be called with any number of arguments. As longas the arguments are integral types the compilation succeeds andfun cansafely do its job.

23.13.5: Applying concepts to free functions

Concepts are most often used in combination with classes. But concepts canalso be used with mere function templates, restricting their argument types.Once a concept is applied to a function that function automatically becomes afunction template. Usually that's clear as a template header is used, buta function can also define constrained parameter types without having toprovide its definition with a template header.

To illustrate the various ways concepts can be used when defining functiontemplates the conceptAddable (cf. section23.13.1) is used in thefollowing examples.

These variants allow us to specify the requirements in the most flexibleway. E.g., if the parameters should also be integral values, then theAddable requirement is not enough, by we also need thestd::integralrequirement, resulting in a function definition like

    template <typename Type>        requires Addable<Type> and std::integral<Type>    auto add(Type const &lhs, Type const &rhs)     {        return lhs + rhs;    }
(which can also be used with the trailingrequires specification).

If theAddable concept completely covers the arguments' requirements, thenthe following abbreviated definitions can be used:

23.13.6: Implementing constrained class members

When defining members of class templates outside of their class interfaces themembers' template headers must match the class templates' templateheaders. This is no different when using concepts.

In the following example the conceptAddable is used when defining theclass templateData. The classData declares a memberprocess, implemented below the class interface. Like the class ofwhich it is a member its header must also specifyAddable (cf. section23.13.1):

    template <Addable Type>    class Data    {        void process();    };    template <Addable Tp>       // The concept must be specified,    void Data<Tp>::process()    // but the formal type name     {                           // doesn't have to be `Type'        ...    }

Comparably, if a class template member function can only be used when aconstraint has been satisfied (but no additional constraints apply to otherclass members), the class template's header can usetypename and the(additional) constraint can be tailored to members where applicable:

    template <typename Type>    // generic template type parameter    class Data    {        void process() requires Addable<Type>;  // additional requirement    };    template <typename X>    void Data<X>::process() requires Addable<X>    ...

Types of member templates themselves may also be constrained. Here too therule applies that the template headers of member implementations must matchthose of their declarations:

    template <typename Type>    class Data    {        template <Addable Tp>       // constraint applied to        void process(Tp par);       // a member template    };    template <typename Type>    template <Addable Tp>    void Data<Type>::process(Tp par)    {        ...    }

23.13.7: Constrained partial specializations

Class templates can be (partially) specialized. Specializations are commonlyused to fine-tune implementations for specific types. Concepts can also beused when specializations are defined. Consider astruct Handler havingthe following generic implementation:
    template <typename Tp>    struct Handler    {        Handler()        {            std::cout << "Generic Handler\n";        }    };

In addition to possibly type-related specializations (like astructHandler<Tp *> ...) a specialization requiring the availability of theaddition operator onTp can be defined by requiring the conceptAddable:

    template <Addable Tp>       // constrain Tp to addable types    struct Handler<Tp>    {        Handler()        {            std::cout << "Handler for types supporting operator+\n";        }    };

When used in the following program (assuming all required headers wereincluded), the first line of the output showsGeneric Handler, whilethe second line showsHandler for types supporting operator+:

    int main()    {        Handler<std::vector<int>>{};    // generic        Handler<int>{};                 // specialized    }

The compiler, compilingmain's first statement, first looks fora specialized version ofHandler. Although it finds one, thatspecialization requires the availability ofoperator+. As that operator isnot available forstd::vector the compiler does not use thatspecialization. Had this been the only available implementation, then thecompiler would have reported aconstraints not satisfied error. However,there's still the generic definition whichcan be used forstd::vector. Therefore the compiler uses the generic definition (which isat the same time provides a nice illustration of the SFINAE (cf. section21.15) principle).

When instantiating the secondHandler object the addition operatorisavailable, and so in that case the compiler selects the specialized version:where available, specializations are used; if not, then generic templatedefinitions are used.

23.13.7.1: Function- and class template declarations

Constrained function- or class-templates can be declared as usual:instead of the implementations semicolons are used. When declaring a function-or class-template without constraint specifications then the function or classtemplate is unconstrained and won't match existing constrained overloadedversions of such function or class templates. On the other hand, concepts cannot be declared. So if a conceptdefinition must be used in multiple source or header files then the conceptdefinition normally is provided in a header file of its own which is thenincluded by files using the concept.

Here are some simple examples illustrating how constrained function templates are declared:

    template <typename Type>    // advice: define concepts in    concept Addable =           // separate headers.        requires(Type lh, Type rh)        {            lh + rh;        };        template <typename Type>    // declares an unconstrained    void fun();                 // function template        template <Addable Type>     // declares a constrained overloaded    void fun();                 // function template        template <typename Type>    // same, requirement follows fun    void fun() requires Addable<Type>;        template <typename Type>    // same, requirement precedes fun    requires Addable<Type> void fun();

When declaring class templates their requires-clauses must precede the classnames. Also, when unconstrained class templates are available the constrainedclass templates are in fact specializations and must be declared accordingly:

    template <typename Type>    // unconstrained    struct Data;                // declaration        template <Addable Type>     // constrained declaration    struct Data<Type>;          // (i.e., a specialization)        template <typename Type>    // same specialization    requires Addable<Type> struct Data<Type>;

Multiple constraints can also be declared:

    template <typename Type>        // used concepts    concept C1 = true;    template <typename Type>    concept C2 = true;        template <C1 Type>              // multiply constrained    requires C2<Type> void fun();   //    function template        template <typename Type>        // same, using 'and'    requires C1<Type> and C2<Type> void fun();        template <typename Type>        // same, trailing 'requires'    void fun() requires C1<Type> and C2<Type>;        template <typename Type>    struct Multi;        template <C1 Type>              // multiply constrained    requires C2<Type> struct Multi<Type>; // class template

Although specializations may define different constraints (e.g., there mayalso be a conceptSubtractable), aData specialization forsubtractable types might also be defined:

    template <Subtractable Type>    struct Data<Type>    {};

But this is probably not what you want: when definingData<vector<int>>{},wheretemplate<typename Type> Data is merely declared, the compilercomplains about anincomplete type `struct Data<std::vector<int>>' as itcannot use the specialization for eitherAddable orSubtractable. Soit falls back on the generic template, but for that one no implementation isavailable, and hence it's incomplete.

Defining a template requiring two types, the first beingAddable and thesecond template argument being unrestricted, while a specialization is definedrequiring aSubtractable type and anint, then that alsodoes'n work as intended. In that case, the templates might be:

    template <typename t1, typename t2> requires Addable<t1>    struct Data    {};        template <Subtractable Type>    struct Data<Type, int>    {};

Here, if the first template argument isn't a subtractable type (like avector<int>), and the second argumentis anint then the compilersimply won't use it because the 1st argument isn't a subtractable type.

Therefore it falls back to the first (generic) template definition. However,that one doesn't work either, because the first argument also isn'taddable, and you receive complaints about(lh + rh) being ill-formed.

Now, as you specifiedint as the template's second argument chances arethat you expected a complaint about(lh - rh) being ill formed, but thatdoesn't happen. In other words: using concepts still requires you tounderstand what's going on. Concepts help the compiler to pinpoint reasonsfor compilation failures, but in the end it's you who has to understand whatyou're doing in order to grasp what the compiler is trying to tell you.

23.13.7.2: Bound free-operators

Earlier, in section22.10.2.1 the construction of free operators ofnested classes of template classes was covered. There the nested classesdefined typenames which were thereupon used to select the appropriate freeoperators by defining template parameters of the typenames that were definedby the nested classes.

Concepts provide yet another way to define free operators, bound to the types ofthe nested classes' template types. When using concepts the classtemplates and their nested classes can be defined in their most basic form, asin:

    template <typename Data>    struct String    {        struct iterator        {            using value_type        = Data;                std::string::iterator d_iter;                    // Note the <>: operator== is a function template                // specialization as 'iterator' is a class template            friend bool operator==<>(iterator const &lhs, iterator const &rhs);        };        iterator begin()        {            return iterator{};        }    };

Once the class interface (struct String) has been specified the conceptcan be formulated. It simply requires that the arguments of the free operatorsareString<Data>::iterator objects:

    template<typename Type>    concept StringIterator =        std::same_as<Type,                     typename String<typename Type::value_type>::iterator>;

The free operator(s) can now be defined as a function template using theabbreviatedStringIterator auto type specification:

    inline bool operator==(StringIterator auto const &lhs,                           StringIterator auto const &rhs)    {        return lhs.d_iter == rhs.d_iter;    }

By using concepts when defining free operators of nested classes of classtemplates we achieve that those operators are bound to the template types ofthose class templates, and that the free operators perfectly match those(nested) classes. Furthermore, when designing the class templates the softwareengineer can concentrate on the class's essential characteristics withouthaving to consider special type-definitions, which are required when using thesfinae approach covered in section22.10.2.1.




[8]ページ先頭

©2009-2025 Movatter.jp