STL
(chapter 18) we've
already used these constructs, commonly known as the
template mechanism.
The template mechanism allows us to specify classes and algorithms, fairly independently of the actual types for which the templates are eventually going to be used. Whenever the template is used, the compiler generates code that is tailored to the particular data type(s) used with the template. This code is generated at compile-time from the template's definition. The piece of generated code is called an instantiation of the template.
In this chapter the syntactic peculiarities of templates are covered. The notions of template type parameter, template non-type parameter, and function template are introduced and several examples of templates are provided (both in this chapter and in chapter 25). Template classes are covered in chapter 22. For good reasons variadic functions are deprecated in C++. However, variadic templates tell us a completely different story, and variadic templates are perfectly acceptable. Both function- and class-templates can be defined as variadic templates. Both forms are covered in section 22.5.
Templates already offered by the language include the abstract containers
(cf. chapter 12); the string
(cf. chapter 5);
streams (cf. chapter 6); and the generic algorithms (cf. chapter
19). So, templates play a central role in present-day C++, and
should not be considered an esoteric feature of the language.
Templates should be approached somewhat similarly as generic algorithms: they're a way of life; a C++ software engineer should actively look for opportunities to use them. Initially, templates may appear to be rather complex and you might be tempted to turn your back on them. However, over time their strengths and benefits are more and more appreciated. Eventually you'll be able to recognize opportunities for using templates. That's the time where your efforts should no longer focus on constructing ordinary functions and classes (i.e., functions or classes that are not templates), but on constructing templates.
This chapter starts by introducing function templates. The emphasis is on the required syntax. This chapter lays the foundation upon which the other chapters about templates are built.
add
expects two Type
arguments and
returns their sum:
Type add(Type const &lhs, Type const &rhs) { return lhs + rhs; }
Note how closely the above function's definition follows its description.
It receives two arguments, and returns its sum. Now consider what would happen
if we defined this function for, e.g., int
values. We would write:
int add(int const &lhs, int const &rhs) { return lhs + rhs; }
So far, so good. However, were we to add two doubles, we would overload this function:
double add(double const &lhs, double const &rhs) { return lhs + rhs; }
There is no end to the number of overloaded versions we might be forced to
construct: an overloaded version for string
, for size_t
, for .... In
general, we would need an overloaded version for every type supporting
operator+
and a copy constructor. All these overloaded versions of
basically the same function are required because of the strongly typed nature
of C++. Because of this, a truly generic function cannot be constructed
without resorting to the template mechanism.
Fortunately, we've already seen an important part of a template
function. Our initial function add
actually is an implementation of such a
function although it isn't a full template definition yet. If we gave the
first add
function to the compiler, it would produce an error message
like:
error: `Type' was not declared in this scope error: parse error before `const'
And rightly so, as we failed to define Type
. The error is prevented
when we change add
into a full template definition. To do this, we look
at the function's implementation and decide that Type
is actually a
formal typename. Comparing it to the alternate implementations, it is
clear that we could have changed Type
into int
to get the first
implementation, and into double
to get the second.
The full template definition allows for this formal nature of the
Type
typename. Using the keyword template
, we prefix one line to
our initial definition, obtaining the following function template
definition:
template <typename Type> Type add(Type const &lhs, Type const &rhs) { return lhs + rhs; }In this definition we distinguish:
template
, starting a template definition or
declaration.
template
. This is a
list containing one or more comma-separated elements. This angle bracket
enclosed list is called the
template parameter list. Template parameter lists using multiple
elements could look like this:
typename Type1, typename Type2
Type
. It is a formal type name, comparable to a formal parameter name in a
function's definition. Up to now we've only encountered formal variable names
with functions. The types of the parameters were always known by the time
the function was defined. Templates escalate the notion of formal names one
step further up the ladder. Templates allow type names to be formalized,
rather than just the variable names themselves. The fact that Type
is a
formal type name is indicated by the keyword typename
, prefixed to
Type
in the template parameter list. A formal type name like Type
is
also called a template type parameter. Template non-type parameters also
exist, and are shortly introduced.
Other texts on C++ sometimes use the keyword class
where we use
typename
. So, in other texts template definitions
might start with a line like:
template <class Type>
In the C++ Annotations the use of typename
over class
is preferred,
reasoning that a template type parameter is, after all, a type name (some
authors prefer class
over typename
; in the end it's a matter of
taste).
template
keyword and the template parameter list is called
the template header.
add
template definition.
Type const &
parameters. This has the usual meaning: the parameters are references to
Type
objects or values that will not be modified by the function.
add
's body, it is clear that operator+
is used, as well as a copy
constructor, as the function returns a value. This allows us to formulate the
following restrictions for the formal type Type
as used by our add
function template:
Type
should support operator+
Type
should support a copy constructor
Type
could be a string
, it could never be
an ostream
, as neither operator+
nor the copy constructor are
available for streams.
template <typename Type> Type add(Type const &lhs, Type const &rhs) { return lhs + rhs; }Look again at
add
's parameters. By specifying Type const &
rather than Type
superfluous copying is prevented, at the same time
allowing values of primitive types to be passed as arguments to the
function. So, when add(3, 4)
is called, int{4}
is assigned to
Type const &rhs
. In general, function parameters should be defined as
Type const &
to prevent unnecessary copying. The compiler is smart enough
to handle `references to references' in this case, which is something the
language normally does not support. For example, consider the following
main
function (here and in the following simple examples it is assumed
that the template and the required headers and namespace declarations have
been provided):
int main() { size_t const &var = size_t{ 4 }; cout << add(var, var) << '\n'; }
Here var
is a reference to a constant size_t
. It is passed as
argument to add
, thereby initializing lhs
and rhs
as Type
const &
to size_t const &
values. The compiler interprets Type
as
size_t
. Alternatively, the parameters might have been specified using
Type &
, rather than Type const &
. The disadvantage of this (non-const)
specification being that temporary values cannot be passed to the function
anymore. The following therefore fails to compile:
int main() { cout << add(string{ "a" }, string{ "b" }) << '\n'; }
Here, a string const &
cannot be used to initialize a string &
.
Had add
defined Type &&
parameters then the above program would have
compiled just fine. In addition the following example correctly compiles as
the compiler decides that Type
apparently is a string const
:
int main() { string const &s = string{ "a" }; cout << add(s, s) << '\n'; }
What can we deduce from these examples?
Type const
&
parameters to prevent unnecessary copying.
Type const &
:
Provided argument: | Actually used Type: |
size_t const | size_t |
size_t | size_t |
size_t * | size_t * |
size_t const * | size_t const * |
As a second example of a function template, consider the following function template:
template <typename Type, size_t Size> Type sum(Type const (&array)[Size]) { Type tp{}; // note: the default constructor must exist. for (size_t idx = 0; idx < Size; idx++) tp += array[idx]; return tp; }
This template definition introduces the following new concepts and features:
size_t
. Template parameters
of specific (i.e., non-formal) types used in template parameter lists are
called
template non-type parameters.
A template non-type parameter defines the type of a
constant expression, which must be known by the time the template is
instantiated and which is specified in terms of existing types, such as a
size_t
.
Type const (&array)[Size]
This parameter defines array
as a reference to an array having
Size
elements of type Type
that may not be modified.
Type
and Size
are
used. Type
is of course the template's type parameter Type
, but
Size
is also a template parameter. It is a size_t
, whose value must be
inferable by the compiler when it compiles an actual call of the sum
function template. Consequently, Size
must be a const
value. Such a
constant expression is called a template non-type parameter, and its type
is named in the template's parameter list.
Type
's concrete value, but also Size
's value. Since the
function sum
only has one parameter, the compiler is only able to infer
Size
's value from the function's actual argument. It can do so if the
provided argument is an array (of known and fixed size) rather than a pointer
to Type
elements. So, in the following main
function the first
statement will compile correctly but the second statement will not:
int main() { int values[5]; int *ip = values; cout << sum(values) << '\n'; // compiles OK cout << sum(ip) << '\n'; // won't compile }
Type tp{}
is used
to define and initialize tp
to a default value. Note here that no fixed
value (like 0) is used. Also, be careful not to use Type tp()
, as this is
a declaration of a function tp
, expecting no arguments, and returning
a Type
. Generally, when it is required to explicitly initialize a value
the empty curly braces should be used. The advantage of explicitly calling the
type's constructor is primarily encountered when Type
is a basic
type. E.g., if Type
is an int
then Type tp{}
initializes tp
to
zero, whereas Type tp
results in tp
having an undefined value.
But all types, even the primitive types, support default constructors
(some classes may choose not to implement a default constructor, or to make it
inaccessible; but most do offer default constructors). The default constructor
of primitive types initializes their
variables to 0 (or false
). Furthermore, the statement Type tp = Type()
is a true initialization: tp
is initialized by Type
's default
constructor, rather than using Type
's copy constructor to assign
Type
's copy to tp
.
It's interesting to note (although not directly related to the
current topic) that the syntactic construction Type tp(Type())
cannot
be used, even though it also looks like a proper initialization. Usually an
initializing argument can be provided to an object's definition, like
string s("hello")
. Why, then, is Type tp = Type()
accepted, whereas
Type tp(Type())
isn't? When Type tp(Type())
is used it won't result
in an error message. So we don't immediately detect that it's not a
Type
object's default initialization. Instead, the compiler starts
generating error messages once tp
is used. This is caused by the fact that
in C++ (and in C alike) the compiler does its best to recognize a
function or
function pointer whenever possible: the
function prevalence rule. According to this rule Type()
is (because
of the pair of parentheses) interpreted as a pointer to a function
expecting no arguments; returning a Type
. The compiler will do so unless
it clearly isn't possible to do so. In the initialization Type tp = Type()
it can't see a pointer to a function as a Type
object cannot be given
the value of a function pointer (remember: Type()
is interpreted as
Type (*)()
whenever possible). But in Type tp(Type())
it can use
the pointer interpretation: tp
is now declared as a function
expecting a pointer to a function returning a Type
, with tp
itself
also returning a Type
. E.g., tp
could have been defined as:
Type tp(Type (*funPtr)()) { return (*funPtr)(); }
sum
also assumes the
existence of certain public members in Type
's class. This time
operator+=
and Type
's copy constructor.
Like class definitions, template definitions should not contain using
directives or declarations: the template might be used in a situation
where such a directive overrides the programmer's intentions: ambiguities or
other conflicts may result from the template's author and the programmer using
different using
directives (E.g, a cout
variable defined in the
std
namespace and in the programmer's own namespace). Instead, within
template definitions only
fully qualified names, including all required namespace specifications should
be used.
auto
keyword was introduced. The keyword
decltype
, related to auto
, shows somewhat different behavior. This
section concentrates on decltype
. Different from auto
, which requires
no further specifications, decltype
is always followed by an
expression between parentheses (e.g., decltype(variable)
).
As an initial illustration, assume we have a function defining a parameter
std::string const &text
. Inside the function we may encounter the
following two definitions:
auto scratch1{text}; decltype(text) scratch2 = text;
With auto
the compiler deduces a plain type, so scratch1
is a
string
, and copy construction is used to initialize it from
`text
'.
Now consider decltype
: decltype
determines text's
type:
string const &
, which is thereupon used as scratch2's
type: string
const &scratch2
, referring to whatever string text
refers to. This is
decltype's
standard behavior: when provided with a variable's name,
it is replaced by that variable's type.
Alternatively, an expression can be specified when using decltype
. Of
course, a variable is an expression by itself, but in the context of
decltype
we define an `expression' as any expression that is more complex
than just a plain variable specification. But it may be as simple as
(variable)
: the name of a variable between parentheses.
When an expression is used, the compiler determines whether a reference could
be appended to the expression's type. If so, decltype(expression)
is
replaced by the type of such an lvalue reference (so you get
expression-type &
). If not, decltype(expression)
is replaced by the
expression's plain type.
Here are some examples:
int *ptr; decltype(ptr) ref = ptr; // decltype's argument is a plain variable, and so // ptr's type is used: int *ref = ptr. // decltype(ptr) is replaced by int *. // (resulting in two warnings about not-initialized/used variables). int *ptr; decltype( (ptr) ) ref = ptr; // decltype's argument is an expression, and so // int *&ref = ptr is used. // decltype( (ptr) ) is replaced by int *&. int value; decltype(value + value) var = value + value; // decltype's argument is an expression, and so the compiler tries // to replace decltype(...) by int & (int &var = value + value) // since value + value is a temporary, var's type cannot be int & // and so decltype(...) is replaced by int // (i.e., value + value's type) string lines[20]; decltype(lines[0]) ref = lines[6]; // decltype's argument is an expression, so // string &ref = lines[6] is used. // decltype(...) is replaced by string & string &&strRef = string{}; decltype(strRef) ref = std::move(strRef); // decltype's argument is a plain variable so the variable's // type is used: string &&ref = std::move(strRef). // decltype(...) is replaced by string && string &&strRef2 = string{} decltype((strRef2)) ref2 = strRef2; // decltype's argument is an expression, so // string && &ref = strRef is used. This automatically becomes // string &ref = strRef which is OK // decltype is replaced by string &.
In addition to this, decltype(auto)
specifications can be used, in
which case decltype's
rules are applied to auto
. So, auto
is used
to determine the type of the initializing expression. Then, if the
initializing expression is a mere variable, then the expression's type is
used. Otherwise, if a reference can be added to the expression's type then
decltype(auto)
is replaced by a reference to the expression's type. Here
are some examples:
int *ptr; decltype(auto) ptr2 = ptr; // auto produces ptr's type: int *, ptr is a plain variable, so // decltype(auto) is replaced by int * int value; decltype(auto) ret = value + value; // auto produces int, value + value is an expression, so int & is // attempted. However, value + value cannot be assigned to a // reference so the expression's type is used: // decltype(auto) is replaced by int string lines[20]; decltype(auto) line = lines[0]; // auto produces string, lines[0] is an expression, so string & is // attempted. string &line = lines[0] is OK, so // decltype(auto) is replaced by string & decltype(auto) ref = string{} // auto produces string, string{} is an expression, so string & is // attempted. However, string &ref = string{} is not a valid // initialization, so string itself is used: // decltype(auto) is replaced by string
In practice, the decltype(auto)
form is most often encountered with
function templates to define return types. Have a look at the following
struct definition (not using function templates, but illustrating the workings
of decltype(auto)
):
struct Data { vector<string> d_vs; string *d_val = new string[10]; Data() : d_vs(1) {} auto autoFun() const { return d_val[0]; } decltype(auto) declArr() const { return d_val[0]; } decltype(auto) declVect() const { return d_vs[0]; } };
autoFun
returns auto
. Since d_val[0]
is passed to
auto
, auto
is deducing as string
, and the function's return
type is string
;
declArr
returns decltype(auto)
. Since d_val[0]
is an expression, representing a string
, decltype(auto)
is deduced
as string &
, which becomes the function's return type.
declVect
returns decltype(auto)
. Since d_vs[0]
is an expression, representing string
, decltype(auto)
is deduced
as string &
. However, since declVect
is also
a const member, this reference should be a string const &
. This is
recognized by decltype(auto)
, and so the function's return type
becomes string const &
.
If you're wondering why there's no const
in declArr's
return type
while there is one in declVect's
return type then have a look at d_vs
and d_val
: both are constant in the context of their functions, but
d_val
, so a const *
, points to non-const string
objects. So,
declArr
does not have to return a string const &
, whereas
declVect
should return a string const &
.
decltype
is a tool for determining the type of an
expression. To use it an expression to which decltype
is applied must be
available. But what if a function template defines a typename Class
template parameter and the function template should use the return type of the
function Class::fun()
? Since two classes may define members fun
having
different return types, the return type to use is not immediately available.
These kinds of problems are solved by using the function template
std::declval
, defined in the <utility>
header
file. This function template defines one template type parameter, and returns
an rvalue reference to an object of the template type parameter's class,
without actually creating a temporary object. But since an rvalue reference is
available, its fun
function can be called, and the return type of that
function can then be produced by decltype
. There are no specific
requirements for the constructors of the class type that's passed to
declval
. Specifically: it doesn't have to have a default or public
constructor (but access rights are used). Consider this
function template:
template <typename Type> decltype(std::declval<Type>().fun()) value() { return 12.5; }
The function value's
return type is defined as the as yet unknown
Type::fun's
return type.
By defining two structs, both having fun
member functions value's
actual return type can now be returned. This is used in main
where
respectively an int
and a double
is returned, resulting in the output
12 12.5
:
struct Integral { int fun() const; // implementation not required }; struct Real { double fun() const; // same. }; int main() { std::cout << value<Integral>() << ' ' << value<Real>() << '\n'; }
int add(int lhs, int rhs) { return lhs + rhs; }
The above function may be converted to a function template:
template <typename Lhs, typename Rhs> Lhs add(Lhs lhs, Rhs rhs) { return lhs + rhs; }
Unfortunately, when the function template is called as
add(3, 3.4)
the intended return type is probably a double
rather than an
int
. This can be solved by adding an additional template type parameter
specifying the return type but then that type must explicitly be specified:
add<double>(3, 3.4);
Using decltype
(cf. section 3.3.7) to define the return type won't
work as lhs
and rhs
aren't known to the compiler by the time
decltype
is used. Thus the next attempt to get rid of the additional
template type parameter fails to compile:
template <typename Lhs, typename Rhs> decltype(lhs + rhs) add(Lhs lhs, Rhs rhs) { return lhs + rhs; }
The decltype
-based definition of a function's return type may become
fairly complex. This complexity can be reduced by using the
late-specified return type syntax that does allow the use of
decltype
to define a function's return type. It is primarily used with
function templates but it may also be used for ordinary (non-template)
functions:
template <typename Lhs, typename Rhs> auto add(Lhs lhs, Rhs rhs) -> decltype(lhs + rhs) { return lhs + rhs; }
When this function is used in a statement like cout << add(3, 3.4)
the
resulting value will be 6.4, which is most likely the intended result, rather
than 6. As an example how a late-specified return type may reduce the
complexity of a function's return type definition consider the following:
template <typename T, typename U> decltype((*(T*)0)+(*(U*)0)) add(T t, U u);
Kind of hard to read? A term like (*(T*)0)
defines 0, using a C
cast, as a pointer to type T
and then dereferences the pointer,
producing a value of type T
(even though that value itself doesn't
exist as a variable). Likewise for the second term that's used in the
decltype
expression. The resulting type is thereupon used as add's
return type.
Using a late-specified return type we get the equivalent:
template <typename T, typename U> auto add(T t, U u) -> decltype(t+u);
which most people consider easier to understand.
The expression specified with decltype
does not necessarily use the
parameters lhs
and rhs
themselves. In the next function
definition lhs.length
is used instead of lhs
itself:
template <typename Class, typename Rhs> auto add(Class lhs, Rhs rhs) -> decltype(lhs.length() + rhs) { return lhs.length() + rhs; }
Any variable visible at the time decltype
is compiled can be used
in the decltype
expression. It is also possible to
handle member selection through pointers to members. The following code aims
at specifying the address of a member function as add
's first argument and
then use its return value type to determine the function template's return
type. Here is an example:
std::string global{"hello world"}; template <typename MEMBER, typename RHS> auto add(MEMBER mem, RHS rhs) -> decltype((global.*mem)() + rhs) { return (global.*mem)() + rhs; } int main() { std::cout << add(&std::string::length, 3.4) << '\n'; // shows: 14.4 }
<functional>
header file must be included.
Situations exist where the compiler is unable to infer that a reference rather
than a value is passed to a function template. In the following example the
function template outer
receives int x
as its argument and the
compiler dutifully infers that Type
is int
:
template <typename Type> void outer(Type t) { t.x(); } void useInt() { int arg; outer(arg); }Compilation will of course fail (as
int
values don't have x
members) and the compiler nicely reports the inferred type, e.g.:
In function 'void outer(Type) [with Type = int]': ...
Another type of error results from using call
in the next example. Here,
call
is a function template expecting a function-type argument. The
function that's passed to call
is sqrtArg
, defining a reference to a
double
: the variable that's passed to sqrtArg
is modified by
sqrtArg
.
void sqrtArg(double &arg) { arg = sqrt(arg); } template<typename Fun, typename Arg> void call(Fun fun, Arg arg) { fun(arg); cout << "In call: arg = " << arg << '\n'; }The first time
call
is used, call(sqrtArg, value)
will not modify
value
: the compiler infers Arg
to be a double
value, and hence
passes value
by value to call
, thus preventing sqrtArg
to modify
main's
variable.
To change main's
variable value
the compiler must be informed that
value
must be passed by reference. Note that we do not want to
define call
's template parameter as a reference parameter,
as passing arguments by value might be appropriate in other situations.
In these situations the ref(arg)
and cref(arg)
reference wrappers should be used. They accept an
argument and return their argument as a (const) reference-typed argument. To
actually change value
it can be passed to call
using ref(value)
as
shown in the following main
function:
int main() { double value = 3; call(sqrtArg, value); cout << "Passed value, returns: " << value << '\n'; call(sqrtArg, ref(value)); cout << "Passed ref(value), returns: " << value << '\n'; } /* Displays: In call: arg = 1.73205 Passed value, returns: 3 In call: arg = 1.73205 Passed ref(value), returns: 1.73205 */
enum { V1, V2, V3 };
Here, the enum
defines an unnamed or
anonymous type.
When defining a function template, the compiler normally deduces the types of its template type parameters from its arguments:
template <typename T> void fun(T &&t); fun(3); // T is int fun('c'); // T is char
The following, however, can also be used:
fun(V1); // T is a value of the above enum type
Within fun
a T
variable may be defined, even if it's an anonymous
type:
template <typename T> void fun(T &&t) { T var(t); }
Values or objects of locally defined types may also be passed as arguments to function templates. E.g.,
void definer() { struct Local { double dVar; int iVar; }; Local local; // using a local type fun(local); // OK: T is 'Local' }
When the compiler deduces the actual types for template type parameters it
only considers the types of the arguments that are actually
used. Neither local variables nor the function's return value is considered in
this process. This is understandable. When a function is called the compiler
is only certain about the types of the function template's arguments. At the
point of the call it definitely does not see the types of the function's local
variables. Also, the function's return value might not actually be used or may
be assigned to a variable of a subrange (or super-range) type of a deduced
template type parameter. So, in the following example, the compiler won't ever
be able to call fun()
, as it won't be able to deduce the actual type for
the Type
template type parameter.
template <typename Type> Type fun() // can never be called as `fun()' { return Type{}; }
Although the compiler won't be able to handle a call to `fun()
', it
is possible to call fun()
using an
explicit type specification. E.g.,
fun<int>()
calls fun
, instantiated for int
. This is of course
not the same as compiler argument deduction.
In general, when a function has multiple parameters of identical template type parameters, the actual types must be exactly the same. So, whereas
void binarg(double x, double y);
may be called using an int
and a double
, with the int
argument
silently being converted to a double
, a similar function template cannot
be called using an int
and double
argument: the compiler won't by
itself promote int
to double
deciding that Type
should be
double
:
template <typename Type> void binarg(Type const &p1, Type const &p2) {} int main() { binarg(4, 4.5); // ?? won't compile: different actual types }
What, then, are the transformations the compiler applies when deducing the actual types of template type parameters? It performs but three types of parameter type transformations and a fourth one to function template non-type parameters. If it cannot deduce the actual types using these transformations, the function template will not be considered. The transformations performed by the compiler are:
const
modifier to
a non-constant argument type;
int
to size_t
, int
to double
, etc.).
An lvalue-to-rvalue transformation is applied when anrvalue
is required, but anlvalue
is provided. This happens when a variable is used as argument to a function specifying a value parameter. For example,template<typename Type> Type negate(Type value) { return -value; } int main() { int x = 5; x = negate(x); // lvalue (x) to rvalue (copies x) }
An array-to-pointer transformation is applied when the name of an array is assigned to a pointer variable. This is frequently used with functions defining pointer parameters. Such functions frequently receive arrays as their arguments. The array's address is then assigned to the pointer-parameter and its type is used to deduce the corresponding template parameter's type. For example:template<typename Type> Type sum(Type *tp, size_t n) { return accumulate(tp, tp + n, Type()); } int main() { int x[10]; sum(x, 10); }In this example, the location of the array
x
is passed tosum
, expecting a pointer to some type. Using the array-to-pointer transformation,x
's address is considered a pointer value which is assigned totp
, deducing thatType
isint
in the process.
This transformation is most frequently used with function templates defining a parameter which is a pointer to a function. When calling such a function the name of a function may be specified as its argument. The address of the function is then assigned to the pointer-parameter, deducing the template type parameter in the process. This is called a function-to-pointer transformation. For example:#include <cmath> template<typename Type> void call(Type (*fp)(Type), Type const &value) { (*fp)(value); } int main() { call(sqrt, 2.0); }In this example, the address of thesqrt
function is passed tocall
, expecting a pointer to a function returning aType
and expecting aType
for its argument. Using the function-to-pointer transformation,sqrt
's address is assigned tofp
, deducing thatType
isdouble
in the process (note thatsqrt
is the address of a function, not a variable that is a pointer to a function, hence the lvalue transformation).The argument
2.0
could not have been specified as2
as there is noint sqrt(int)
prototype. Furthermore, the function's first parameter specifiesType (*fp)(Type)
, rather thanType (*fp)(Type const &)
as might have been expected from our previous discussion about how to specify the types of function template's parameters, preferring references over values. However,fp
's argumentType
is not a function template parameter, but a parameter of the functionfp
points to. Sincesqrt
has prototypedouble sqrt(double)
, rather thandouble sqrt(double const &)
,call
's parameterfp
must be specified asType (*fp)(Type)
. It's that strict.
const
or volatile
qualifications to pointers. This transformation is applied when the
function template's type parameter explicitly specifies const
(or
volatile
) but the function's argument isn't a const
or volatile
entity. In that case const
or volatile
is provided by the compiler.
Subsequently the compiler deduces the template's type parameter. For example:
template<typename Type> Type negate(Type const &value) { return -value; } int main() { int x = 5; x = negate(x); }
Here we see the function template's Type const &value
parameter: a
reference to a const Type
. However, the argument isn't a const int
,
but an int
that can be modified. Applying a qualification transformation,
the compiler adds const
to x
's type, and so it matches int const
x
. This is then matched against Type const &value
allowing the compiler
to deduce that Type
must be int
.
In section 22.11 it is shown how a class template can be derived from another class template.
As class template derivation remains to be covered, the following discussion is necessarily somewhat premature. The reader may of course skip briefly to section 22.11 returning back to this section thereafter.
In this section it should be assumed, for the sake of argument, that a
class template Vector
has somehow been derived from a std::vector
.
Furthermore, assume that the following function template has been
constructed to sort a vector using some function object obj
:
template <typename Type, typename Object> void sortVector(std::vector<Type> vect, Object const &obj) { sort(vect.begin(), vect.end(), obj); }
To sort std::vector<string>
objects case-insensitively, a class
Caseless
could be constructed as follows:
class CaseLess { public: bool operator()(std::string const &before, std::string const &after) const { return strcasecmp(before.c_str(), after.c_str()) < 0; } };
Now various vectors may be sorted using sortVector()
:
int main() { std::vector<string> vs; std::vector<int> vi; sortVector(vs, CaseLess()); sortVector(vi, less<int>()); }
Applying the transformation
transformation to a base class instantiated from a class template, the
function template sortVector
may now also be used to sort Vector
objects. For example:
int main() { Vector<string> vs; // `Vector' instead of `std::vector' Vector<int> vi; sortVector(vs, CaseLess()); sortVector(vi, less<int>()); }
In this example, Vector
s were passed as argument to
sortVector
. Applying the transformation to a base class instantiated from
a class template, the compiler considers Vector
to be a std::vector
enabling it to deduce the template's type parameter. A std::string
for the
Vector vs
, an int
for Vector vi
.
Type
is int
if the argument is int x
, and
the function's parameter is Type &value
).
int
and a double
argument:
template <typename Type> Type add(Type const &lhs, Type const &rhs) { return lhs + rhs; }
When calling this function template, two identical types must be used (albeit that the three standard transformations are of course allowed). If the template deduction mechanism does not come up with identical actual types for identical template types, then the function template is not going to be instantiated.
In such cases the compiler performs type contractions. Doubling identical
reference types results in a simple contraction: the type is deduced to be a
single reference type. Example: if the template parameter type is specified as
a Type &&
and the actual parameter is an int &&
then Type
is
deduced to be an int
, rather than an int &&
.
This is fairly intuitive. But what happens if the actual type is int &
?
There is no such thing as an int & &¶m
and so the compiler contracts
the double reference by removing the rvalue reference, keeping the lvalue
reference. Here the following rules are applied:
1. A function template parameter defined as an lvalue reference to a template's type parameter (e.g.,Examples:Type &
) receiving an lvalue reference argument results in a single lvalue reference.2. A function template parameter defined as an rvalue reference to a template's type parameter (e.g.,
Type &&
) receiving any kind of reference argument uses the reference type of the argument.
Actual &
argument then Type &
becomes an
Actual &
and Type
is inferred as Actual
;
Actual &
then Type &&
becomes an
Actual &
and Type
is inferred as Actual
;
Actual &&
then Type &
also becomes
Actual &
and Type
is inferred as Actual
;
Actual &&
then Type &&
becomes Actual
&&
and Type
is inferred as Actual
;
Let's look at a concrete exampe where contraction occurs. Consider the following function template where a function parameter is defined as an rvalue references to some template type parameter:
template <typename Type> void function(Type &¶m) { callee(static_cast<Type &&>(param)); }
In this situation, when function
is called with an (lvalue) argument of
type TP &
the template type parameter Type
is deduced to be Tp
&
. Therefore, Type &¶m
is instantiated as Tp ¶m
, Type
becomes Tp
and the rvalue reference is replaced by an lvalue reference.
Likewise, when callee
is called using the static_cast
the same
contraction occurs, so Type &¶m
operates on Tp ¶m
. Therefore
(using contraction) the static cast also uses type Tp ¶m
. If
param
happened to be of type Tp &&
then the static cast uses type
Tp &¶m
.
This characteristic allows us to pass a function argument to a nested function without changing its type: lvalues remain lvalues, rvalues remain rvalues. This characteristic is therefore also known as perfect forwarding which is discussed in greater detail in section 22.5.2. Perfect forwarding prevents the template author from having to define multiply overloaded versions of a function template.
algorithm
on my old laptop takes about four times the amount of time it takes to compile
a plain header file like cmath
. The header file iostream
is even
harder to process, requiring almost 15 times the amount of time it takes to
process cmath
. Clearly, processing templates is serious business for the
compiler. On the other hand this drawback shouldn't be taken too seriously.
Compilers are continuously improving their template processing capacity and
computers keep getting faster and faster. What was a nuisance a few years ago
is hardly noticeable today.
When templates are declared, the compiler does not have to process the template's definitions again and again; and no instantiations are created on the basis of template declarations alone. Any actually required instantiation must then be available elsewhere (of course, this holds true for declarations in general). Unlike the situation we encounter with ordinary functions, which are usually stored in libraries, it is currently not possible to store templates in libraries (although the compiler may construct precompiled header files). Consequently, using template declarations puts a burden on the shoulders of the software engineer, who has to make sure that the required instantiations exist. Below a simple way to accomplish that is introduced.
To create a function template declaration simply replace the function's
body by a semicolon. Note that this is exactly identical to the way ordinary
function declarations are constructed. So, the previously defined function
template add
can simply be declared as
template <typename Type> Type add(Type const &lhs, Type const &rhs);
We've already encountered template declarations. The header file
iosfwd
may be included in sources not requiring instantiations of elements
from the class ios
and its derived classes. For example, to compile the
declaration
std::string getCsvLine(std::istream &in, char const *delim);
it is not necessary to include the string
and istream
header
files. Rather, a single
#include <iosfwd>
is sufficient. Processing iosfwd
requires only a fraction of the time
it takes to process the string
and istream
header files.
For this a variant of a template declaration is available, a so-called explicit instantiation declaration. An explicit instantiation declaration consists of the following elements:
template
, omitting the template
parameter list.
Using explicit instantiation declarations all instantiations of template
functions required by a program can be collected in one file. This file, which
should be a normal source file, should include the template definition
header file and should subsequently specify the required explicit
instantiation declarations. Since it's a source file, it is not included
by other sources. So namespace using
directives and declarations may
safely be used once the required headers have been included. Here is an
example showing the required instantiations for our earlier add
function
template, instantiated for double
, int
, and std::string
types:
#include "add.h" #include <string> using namespace std; template int add<int>(int const &lhs, int const &rhs); template double add<double>(double const &lhs, double const &rhs); template string add<string>(string const &lhs, string const &rhs);
If we're sloppy and forget to mention an instantiation required by our program then the repair is easily made by adding the missing instantiation declaration to the above list. After recompiling the file and relinking the program we're done.
So, when is a function template actually instantiated? There are two situations where the compiler decides to instantiate templates:
add
is called with a pair of size_t
values);
char (*addptr)(char const &, char const &) = add;
The compiler is not always able to deduce the template's type parameters unambiguously. When the compiler reports an ambiguity it must be solved by the software engineer. Consider the following code:
#include <iostream> #include "add.h" size_t fun(int (*f)(int *p, size_t n)); double fun(double (*f)(double *p, size_t n)); int main() { std::cout << fun(add); }When this little program is compiled, the compiler reports an ambiguity it cannot resolve. It has two candidate functions as for each overloaded version of
fun
an add
function can be instantiated:
error: call of overloaded 'fun(<unknown type>)' is ambiguous note: candidates are: int fun(size_t (*)(int*, size_t)) note: double fun(double (*)(double*, size_t))
Such situations should of course be avoided. Function templates can only
be instantiated if there's no ambiguity. Ambiguities arise when multiple
functions emerge from the compiler's function selection mechanism (see section
21.14). It is up to us to resolve the ambiguities. They
could be resolved using a blunt static_cast
(by which we select among
alternatives, all of them possible and available):
#include <iostream> #include "add.h" int fun(int (*f)(int const &lhs, int const &rhs)); double fun(double (*f)(double const &lhs, double const &rhs)); int main() { std::cout << fun( static_cast<int (*)(int const &, int const &)>(add) ); }But it's good practice to avoid type casts wherever possible. How to do this is explained in the next section (21.7).
source1.cc
defines a function fun
, instantiating
add
for int
-type arguments, including add
's template
definition. It displays add
's address using union PointerUnion
:
union PointerUnion { int (*fp)(int const &, int const &); void *vp; };
Here is a program using PointerUnion
:
#include <iostream> #include "add.h" #include "pointerunion.h" void fun() { PointerUnion pu = { add }; std::cout << pu.vp << '\n'; }
source2.cc
defines the same function, but merely declares the
proper add
template using a template declaration (not an instantiation
declaration). Here is source2.cc
:
#include <iostream> #include "pointerunion.h" template<typename Type> Type add(Type const &, Type const &); void fun() { PointerUnion pu = { add }; std::cout << pu.vp << '\n'; }
main.cc
again includes add
's template definition,
declares the function fun
and defines main
, defining add
for int
-type arguments as well and displaying add
's function
address. It also calls the function fun
. Here is main.cc
:
#include <iostream> #include "add.h" #include "pointerunion.h" void fun(); int main() { PointerUnion pu = { add }; fun(); std::cout << pu.vp << '\n'; }
source1.o
(1912 bytes using g++
version 4.3.4 (sizes of object
modules reported in this section may differ for different compilers and/or
run-time libraries)) and source2.o
(1740 bytes). Since source1.o
contains the instantiation of add
, it is somewhat larger than
source2.o
, containing only the template's declaration. Now we're ready to
start our little experiment.
main.o
and source1.o
, we obviously link together two
object modules, each containing its own instantiation of the same template
function. The resulting program produces the following output:
0x80486d8 0x80486d8
Furthermore, the size of the resulting program is 6352 bytes.
main.o
and source2.o
, we now link together an object
module containing the instantiation of the add
template, and another
object module containing the mere declaration of the same template
function. So, the resulting program cannot but contain a single instantiation
of the required function template. This program has exactly the same size, and
produces exactly the same output as the first program.
fun
) existed, expecting different types of arguments. The
ambiguity resulted from the fact that both arguments could have been provided
by an instantiation of a function template. The intuitive way to solve such an
ambiguity is to use a static_cast
. But casts should be avoided wherever
possible.
With function templates static casts may indeed be avoided using explicit template type arguments. Explicit template type arguments can be used to inform the compiler about the actual types it should use when instantiating a template. To use explicit type arguments the function's name is followed by an actual template type argument list which may again be followed by the function's argument list. The actual types mentioned in the actual template argument list are used by the compiler to `deduce' what types to use when instantiating the template. Here is the example from the previous section, now using explicit template type arguments:
#include <iostream> #include "add.h" int fun(int (*f)(int const &lhs, int const &rhs)); double fun(double (*f)(double const &lhs, double const &rhs)); int main() { std::cout << fun(add<int>) << '\n'; }
Explicit template type arguments can be used in situations where the
compiler has no way to detect which types should actually be used. E.g., in
section 21.4 the function template Type fun()
was defined. To
instantiate this function for the double
type, we can call
fun<double>()
.
add
template. That template was designed to
return the sum of two entities. If we would want to compute the sum of three
entities, we could write:
int main() { add(add(2, 3), 4); }
This is an acceptable solution for the occasional situation. However, if
we would have to add three entities regularly, an overloaded version of
the add
function expecting three arguments might be a useful function to
have. There's a simple solution to this problem: function templates may be
overloaded.
To define an overloaded function template, merely put multiple definitions
of the template in its header file. For the add
function this would boil
down to:
template <typename Type> Type add(Type const &lhs, Type const &rhs) { return lhs + rhs; } template <typename Type> Type add(Type const &lhs, Type const &mid, Type const &rhs) { return lhs + mid + rhs; }
The overloaded function does not have to be defined in terms of simple values. Like all overloaded functions, a unique set of function parameters is enough to define an overloaded function template. For example, here's an overloaded version that can be used to compute the sum of the elements of a vector:
template <typename Type> Type add(std::vector<Type> const &vect) { return accumulate(vect.begin(), vect.end(), Type()); }
When overloading function templates we do not have to restrict ourselves
to the function's parameter list. The template's type parameter list itself
may also be
overloaded. The last definition of the add
template allows us to
specify a vector
as its first argument, but no deque
or
map
. Overloaded versions for those types of containers could of course be
constructed, but how far should we go? A better approach seems to be to look
for common characteristics of these containers. If found we may be able to
define an overloaded function template based on these common
characteristics. One common characteristic of the mentioned containers is that
they all support begin
and end
members, returning iterators. Using
this, we could define a template type parameter representing containers that
must support these members. But mentioning a plain `container type' doesn't
tell us for what type of data it was instantiated. So we need a second
template type parameter representing the container's data type, thus
overloading the template's type parameter list. Here is the resulting
overloaded version of the add
template:
template <typename Container, typename Type> Type add(Container const &cont, Type const &init) { return std::accumulate(cont.begin(), cont.end(), init); }
One may wonder whether the init
parameter could not be left out of the
parameter list as init
often has a default initialization value. The
answer is `yes', but there are complications. It is possible to define the
add
function as follows:
template <typename Type, typename Container> Type add(Container const &cont) { return std::accumulate(cont.begin(), cont.end(), Type()); }
Note, however, that the template's type parameters were reordered, which
is necessary because the compiler won't be able to determine Type
in a
call like:
int x = add(vectorOfInts);
After reordering the template type parameters, putting Type
first, an
explicit template type argument can be provided for the first template type
parameter:
int x = add<int>(vectorOfInts);
In this example we provided a vector<int>
argument. One might wonder
why we have to specify int
explicitly to allow the compiler to determine
the template type parameter Type
. In fact, we don't. A third kind of
template parameter exists, a template template parameter, allowing the
compiler to determine Type
directly from the actual container
argument. Template template parameters are discussed in section
23.4.
using namespace std; int main() { vector<int> v; add(3, 4); // 1 (see text) add(v); // 2 add(v, 0); // 3 }
int
. It therefore instantiates add<int>
, our very first definition
of the add
template.
add
requiring but one argument. It
finds the overloaded function template expecting a std::vector
, deducing
that the template's type parameter must be int
. It instantiates
add<int>(std::vector<int> const &)
add
template's first definition can't be used. But it can use the
last definition, expecting entities having different types. As a
std::vector
supports begin
and end
, the compiler is now able to
instantiate the function template
add<std::vector<int>, int>(std::vector<int> const &, int const &)
add
function template for two equal and two
different template type parameters we've exhausted the possibilities for using
an add
function template having two template type parameters.
add
this
introduces an ambiguity as the compiler won't be able to choose which of
the two overloaded versions defining two differently typed function parameters
should be used. For example when defining:
#include "add.h" template <typename T1, typename T2> T1 add(T1 const &lhs, T2 const &rhs) { return lhs + rhs; } int main() { add(3, 4.5); }
the compiler reports an ambiguity like the following:
error: call of overloaded `add(int, double)' is ambiguous error: candidates are: Type add(const Container&, const Type&) [with Container = int, Type = double] error: T1 add(const T1&, const T2&) [with T1 = int, T2 = double]
Now recall the overloaded function template accepting three arguments:
template <typename Type> Type add(Type const &lhs, Type const &mvalue, Type const &rhs) { return lhs + mvalue + rhs; }
It may be considered as a disadvantage that only equally typed arguments
are accepted by this function (three int
s, three double
s, etc.). To
remedy this we define yet another overloaded function template, this time
accepting arguments of any type. This function template can only be used if
operator+
is defined between the function's actually used types, but apart
from that there appears to be no problem. Here is the overloaded version
accepting arguments of any type:
template <typename Type1, typename Type2, typename Type3> Type1 add(Type1 const &lhs, Type2 const &mid, Type3 const &rhs) { return lhs + mid + rhs; }
Now that we've defined the above two overloaded function templates
expecting three arguments let's call add
as follows:
add(1, 2, 3);
Should we expect an ambiguity here? After all, the compiler might select
the former function, deducing that Type == int
, but it might also select
the latter function, deducing that Type1 == int, Type2 == int
and Type3
== int
. Remarkably, the compiler reports no ambiguity.
No ambiguity is reported because of the following. If overloaded template functions are defined using less and more specialized template type parameters (e.g., less specialized: all types different vs. more specialized: all types equal) then the compiler selects the more specialized function whenever possible.
As a rule of thumb: overloaded function templates must allow a unique combination of template type arguments to be specified to prevent ambiguities when selecting which overloaded function template to instantiate. The ordering of template type parameters in the function template's type parameter list is not important. E.g., trying to instantiate one of the following function templates results in an ambiguity:
template <typename T1, typename T2> void binarg(T1 const &first, T2 const &second) {} template <typename T1, typename T2> void binarg(T2 const &first, T1 const &second) {}
This should not come as a surprise. After all, template type parameters
are just formal names. Their names (T1
, T2
or Whatever
) have no
concrete meanings.
add
accepting certain containers:
template <typename Container, typename Type> Type add(Container const &container, Type const &init);
template int add<std::vector<int>, int> (std::vector<int> const &vect, int const &init);
std::vector<int> vi; int sum = add<std::vector<int>, int>(vi, 0);
add
template, defining two identically typed parameters
works fine for all types supporting operator+
and a copy
constructor. However, these assumptions are not always met. For example, with
char *
s, using operator+
or a `copy constructor' does not make
sense. The compiler tries to instantiate the function template, but
compilation fails as operator+
is not defined for pointers.
In such situations the compiler may be able to resolve the template type parameters but it (or we ...) may then detect that the standard implementation is pointless or produces errors.
To solve this problem a template explicit specialization may be defined. A template explicit specialization defines the function template for which a generic definition already exists using specific actual template type parameters. As we saw in the previous section the compiler always prefers a more specialized function over a less specialized one. So the template explicit specialization is selected whenever possible.
A template explicit specialization offers a specialization for its template
type parameter(s). The special type is consistently substituted for
the template type parameter in the function template's code. For
example if the explicitly specialized type is char const *
then in the
template definition
template <typename Type> Type add(Type const &lhs, Type const &rhs) { return lhs + rhs; }
Type
must be replaced by char const *
, resulting in a function
having prototype
char const *add(char const *const &lhs, char const *const &rhs);
Now we try to use this function:
int main(int argc, char **argv) { add(argv[0], argv[1]); }
However, the compiler ignores our specialization and tries to instantiate the initial function template. This fails, leaving us wondering why it didn't select the explicit specialization....
To see what happened here we replay, step by step, the compiler's actions:
add
is called with char *
arguments.
Type
equals
char *
.
char *
template type
argument match a char const *const &
template parameter? Here
opportunities for the allowable transformations from section 21.4
may arise. A qualification transformation seems to be the only viable one,
allowing the compiler to bind a const-parameter to a non-const argument.
Type
the compiler can match an argument of some
Type
or an argument of some Type const
to a Type const &
.
Type
itself is not modified, and so Type
is a char *
.
char const *
.
char const *
is not a char *
it rejects the explicit
specialization and uses the generic form, resulting in a compilation error.
add
function template should also be able to handle char *
template type arguments another explicit specialization for char *
may be
required, resulting in the prototype
char *add(char *const &lhs, char *const &rhs);
Instead of defining another explicit specialization an overloaded
function template could be designed expecting pointers. The following function
template definition expects two pointers to constant Type
values and
returns a pointer to a non-constant Type
:
template <typename Type> Type *add(Type const *t1, Type const *t2) { std::cout << "Pointers\n"; return new Type; }
What actual types may be bound to the above function parameters? In this
case only a Type const *
, allowing char const *
's to be passed as
arguments. There's no opportunity for a qualification transformation here.
The qualification transformation allows the compiler to add a const
to a
non-const argument if the parameter itself (and not Type
) is
specified in terms of a const
or const &
. Looking at, e.g., t1
we
see that it's defined as a Type const *
. There's nothing const
here
that's referring to the parameter (in which case it would have been Type
const *const t1
or Type const *const &t1
). Consequently a qualification
transformation cannot be applied here.
As the above overloaded function template only accepts char const *
arguments, it will not accept (without a reinterpret cast) char *
arguments. So main
's argv
elements cannot be passed to our overloaded
function template.
Type *
arguments? It is possible, but at some point it
should become clear that our approach doesn't scale. Like ordinary functions
and classes, function templates should have one conceptually clear
purpose. Trying to add overloaded function templates to overloaded function
templates quickly turns the template into a kludge. Don't use this approach. A
better approach is to construct the template so that it fits its original
purpose, to make allowances for the occasional specific case and to describe
its purpose clearly in its documentation.
In some situations constructing template explicit specializations may of
course be defensible. Two specializations for const
and non-const
pointers to characters might be appropriate for our add
function
template. Here's how they are
constructed:
template
.
error: template-id `add<char*>' for `char* add(char* const&, char* const&)' does not match any template declaration
Here are two explicit specializations for the function template add
,
expecting char *
and char const *
arguments:
template <> char *add<char *>(char *const &p1, char *const &p2) { std::string str(p1); str += p2; return strcpy(new char[str.length() + 1], str.c_str()); } template <> char const *add<char const *>(char const *const &p1, char const *const &p2) { static std::string str; str = p1; str += p2; return str.c_str(); }
Template explicit specializations are normally included in the file containing the other function template's implementations.
When declaring a template explicit specialization the pair of
angle brackets following the template
keyword are essential. If
omitted, we would have constructed a
template instantiation declaration.
The compiler would silently process it, at the expense of a somewhat
longer compilation time.
When declaring a template explicit specialization (or when using an
instantiation declaration) the
explicit specification of the template type parameters can be omitted if
the compiler is able to deduce these types from the function's arguments. As
this is the case with the char (const) *
specializations, they could also
be declared as follows:
template <> char *add(char *const &p1, char *const &p2) template <> char const *add(char const *const &p1, char const *const &p2);
If in addition template <>
could be omitted the template character
would be removed from the declaration. The resulting declaration is now a mere
function declaration. This is not an error: function templates and ordinary
(non-template) functions may mutually overload each other. Ordinary functions
are not as restrictive as function templates with respect to allowed type
conversions. This could be a reason to overload a template with an ordinary
function every once in a while.
A function template explicit specialization is not just another overloaded version of the function template. Whereas an overloaded version may define a completely different set of template parameters, a specialization must use the same set of template parameters as its non-specialized variant. The compiler uses the specialization in situations where the actual template arguments match the types defined by the specialization (following the rule that the most specialized set of parameters matching a set of arguments will be used). For different sets of parameters overloaded versions of functions (or function templates) must be used.
std::string
conversion
operator (cf. section 11.3).
A conversion operator is guaranteed to be used as an rvalue. This means that
objects of a class defining a string
conversion operator can be assigned
to, e.g., string
objects. But when trying to insert objects defining
string
conversion operators into streams then the compiler complains that
we're attempting to insert an inappropriate type into an ostream
.
On the other hand, when this class defines an int
conversion operator
insertion is performed flawlessly.
The reason for this distinction is that operator<<
is defined as a plain
(free) function when inserting a basic type (like int
) but it is defined
as a function template when inserting a string
. Hence, when trying to
insert an object of our class defining a string
conversion operator the
compiler visits all overloaded versions of insertion operators inserting into
ostream
objects.
Since no basic type conversion is available the basic type insertion operators
can't be used. Since the available conversions for template arguments do not
allow the compiler to look for conversion operators our class defining the
string
conversion operator cannot be inserted into an ostream
.
If it should be possible to insert objects of such a class into ostream
objects the class must define its own overloaded insertion operator (in
addition to the string
conversion operator that was required to use the
class's objects as rvalue in string
assignments).
static_assert(constant expression, error message)
utility is available to allow assertions to be made from inside template definitions. Here are two examples of its use:
static_assert(BUFSIZE1 == BUFSIZE2, "BUFSIZE1 and BUFSIZE2 must be equal"); template <typename Type1, typename Type2> void rawswap(Type1 &type1, Type2 &type2) { static_assert(sizeof(Type1) == sizeof(Type2), "rawswap: Type1 and Type2 must have equal sizes"); // ... }
The first example shows how to avoid yet another preprocessor directive
(in this case the #error
directive).
The second example shows how static_assert
can be used to ensure that
a template operates under the right condition(s).
The string defined in static_assert
's second argument is displayed and
compilation stops if the condition specified in static_assert
's first
argument is false
.
Like the #error
preprocessor directive static_assert
is a
compile-time matter that doesn't have any effect on the run-time
efficiency of the code in which it is used.
<climits>
defines constants for various types, e.g.,
INT_MAX
defines the maximum value that can be stored in an int
.
The disadvantage of the limits defined in climits
is that they are fixed
limits. Let's assume you write a function template that receives an argument
of a certain type. E.g,
template<typename Type> Type operation(Type &&type);
Assume this function should return the largest negative value for Type
if type
is a negative value and the largest positive value if type
is
a positive value. However, 0 should be returned if the type is not an integral
value.
How to proceed?
Since the constants in climits
can only be used if the type to use is
already known, the only approach seems to be to create function template
specializations for the various integral types, like:
template<> int operation<int>(int &&type) { return type < 0 ? INT_MIN : INT_MAX; }
The facilities provided by numeric_limits provide an alternative. To use
these facilities the header file <limits>
header file must be included.
The class template numeric_limits
offers various members answering all
kinds of questions that could be asked of numeric types. Before introducing
these members, let's have a look at how we could implement the operation
function template as just one single function template:
template<typename Type> Type operation(Type &&type) { return not numeric_limits<Type>::is_integer ? 0 : type < 0 ? numeric_limits<Type>::min() : numeric_limits<Type>::max(); }
Now operation
can be used for all the language's primitive types.
Here is an overview of the facilities offered by numeric_limits
. Note
that the member functions defined by numeric_limits
return constexpr
values. A member `member
' defined by numeric_limits
for type Type
can be used as follows:
numeric_limits<Type>::member // data members numeric_limits<Type>::member() // member functions
Type denorm_min()
:Type
: its minimum positive denormalized value;
otherwise it returns numeric_limits<Type>::min()
.
int digits
:Type
values, or (floating
point types) the number of digits in the mantissa are returned.
int digits10
:Type
value
without changing it.
Type constexpr epsilon()
:Type
between the smallest value exceeding 1 and
1 itself.
float_denorm_style has_denorm
:has_denorm
member returns information
about denormalized values for type Type
:
denorm_absent
: Type
does not allow denormalized values;
denorm_indeterminate
: Type
may or may not use denormalized
values; the compiler cannot determine this at compile-time;
denorm_present
: Type
uses denormalized values;
bool has_denorm_loss
:true
if a loss of accuracy was detected as a result of using
denormalization (rather than being an inexact result).
bool has_infinity
:true
if Type
has a representation for positive infinity.
bool has_quiet_NaN
:true
if Type
has a representation for a non-signaling
`Not-a-Number' value.
bool has_signaling_NaN
:true
if Type
has a representation for a signaling
`Not-a-Number' value.
Type constexpr infinity()
:Type
: its positive infinity value.
bool is_bounded
:true
if Type
contains a finite set of values.
bool is_exact
:true
if Type
uses an exact representation.
bool is_iec559
:true
if Type
uses the IEC-559 (IEEE-754) standard. Such
types always return true
for has_infinity, has_quiet_NaN
and
has_signaling_NaN
, while infinity(), quiet_NaN()
and
signaling_NaN()
return non-zero values.
bool is_integer
:true
if Type
is an integral type.
bool is_modulo
:true
if Type
is a `modulo' type. Values of modulo types can
always be added, but the addition may `wrap around' producing a
smaller Type
result than either of the addition's two operands.
bool is_signed
:true
if Type
is signed.
bool is_specialized
:true
for specializations of Type
.
Type constexpr lowest()
:Type
's lowest finite value representable by Type
:
no other finite value smaller than the value returned by lowest
exists. This value equals the value returned by min
except for
floating-point types.
T constexpr max()
:Type
's maximum value.
T constexpr min()
:Type
's minimum value. For denormalized floating point types the
minimum positive normalized value.
int max_exponent
:Type
producing a valid Type
value.
int max_exponent10
:Type
value.
int min_exponent
:Type
producing a valid Type
value.
int min_exponent10
:Type
value.
Type constexpr quiet_NaN()
:Type
: its a non-signaling `Not-a-Number' value.
int radix
:Type
is an integral type: base of the representation; if
Type
is a floating point type: the base of the exponent of the
representation.
Type constexpr round_error()
:Type
.
float_round_style round_syle
:Type
. It has one of the following
enum
float_round_style
values:
round_toward_zero
: values are rounded towards zero;
round_to_nearest
: values are rounded to the nearest representable
value;
round_toward_infinity
:values are rounded towards infinity;
round_toward_neg_infinity
: if it rounds towards negative
infinity;
round_indeterminate
: if the rounding style is indeterminable at
compile-time.
Type constexpr signaling_NaN()
:Type
: its a signaling `Not-a-Number' value.
bool tinyness_before
:true
if Type
allows tinyness to be detected before rounding.
bool traps
:true
if Type
implements trapping.
To solve this problem polymorphous (function object) wrappers can be used. Polymorphous wrappers refer to function pointers, member functions or function objects, as long as their parameters match in type and number.
Before using polymorphic function wrappers the <functional>
header file
must be included.
Polymorphic function wrappers are made available through the
std::function
class template. Its template argument
is the prototype of the function to create a wrapper for. Here is an example
of the definition of a polymorphic function wrapper that can be used to point
to a function expecting two int
values and returning an int
:
std::function<int (int, int)> ptr2fun;
Here, the template's parameter is int (int, int)
, indicating a
function expecting two int
arguments, and returning and int
. Other
prototypes return other, matching, function wrappers.
Such a function wrapper can now be used to point to any function the wrapper
was created for. E.g., `plus<int> add
' creates a functor defining an
int operator()(int, int)
function call member. As this qualifies as a
function having prototype int (int, int)
, our ptr2fun
may point to
add
:
ptr2fun = add;
If ptr2fun
does not yet point to a function (e.g., it is merely
defined) and an attempt is made to call a function through it a
`std::bad_function_call
'
exception is thrown. Also, a polymorphic function wrapper that hasn't been
assigned to a function's address represents the value false
in logical
expressions (as if it had been a pointer having value zero):
std::function<int(int)> ptr2int; if (not ptr2int) cout << "ptr2int is not yet pointing to a function\n";
Polymorphous function wrappers can also be used to refer to functions, functors or other polymorphous function wrappers having prototypes for which standard conversions exist for either parameters or return values. E.g.,
bool predicate(long long value); void demo() { std::function<int(int)> ptr2int; ptr2int = predicate; // OK, convertible param. and return type struct Local { short operator()(char ch); }; Local object; std::function<short(char)> ptr2char(object); ptr2int = object; // OK, object is a functor whose function // operator has a convertible param. and // return type. ptr2int = ptr2char; // OK, now using a polym. funct. wrapper }
add
function template:
template <typename Container, typename Type> Type add(Container const &container, Type init) { return std::accumulate(container.begin(), container.end(), init); }
Here std::accumulate
is called using container
's begin
and
end
members.
The calls container.begin()
and container.end()
are said to
depend on template type parameters. The compiler, not having seen
container
's interface, cannot check whether container
actually
has members begin
and end
returning input iterators.
On the other hand, std::accumulate
itself is independent of any
template type parameter. Its arguments depend on template parameters, but
the function call itself isn't. Statements in a template's body that are
independent of template type parameters are said not to depend on template
type parameters.
When the compiler encounters a template definition, it verifies the
syntactic correctness of all statements not depending on template
parameters. I.e., it must have seen all class definitions, all type
definitions, all function declarations etc. that are used in those statements.
If the compiler hasn't seen the required definitions and declarations then it
will reject the template's definition. Therefore, when submitting the above
template to the compiler the numeric
header file must first have been
included as this header file declares std::accumulate
.
With statements depending on template parameters the compiler cannot
perform those extensive syntactic checks. It has no way to verify the
existence of a member begin
for the as yet unspecified type
Container
. In these cases the compiler performs superficial checks,
assuming that the required members, operators and types eventually become
available.
The location in the program's source where the template is instantiated is called its point of instantiation. At the point of instantiation the compiler deduces the actual types of the template's parameters. At that point it checks the syntactic correctness of the template's statements that depend on template type parameters. This implies that the compiler must have seen the required declarations only at the point of instantiation. As a rule of thumb, you should make sure that all required declarations (usually: header files) have been read by the compiler at every point of instantiation of the template. For the template's definition itself a more relaxed requirement can be formulated. When the definition is read only the declarations required for statements not depending on the template's type parameters must have been provided.
Assume we ask the compiler to compile the following main
function:
int main() { process(3, 3); }
Furthermore assume that the compiler has encountered the following
function declarations when it's about to compile main
:
template <typename T> void process(T &t1, int i); // 1 template <typename T1, typename T2> void process(T1 const &t1, T2 const &t2); // 2 template <typename T> void process(T const &t, double d); // 3 template <typename T> void process(T const &t, int i); // 4 template <> void process<int, int>(int i1, int i2); // 5 void process(int i1, int i2); // 6 void process(int i, double d); // 7 void process(double d, int i); // 8 void process(double d1, double d2); // 9 void process(std::string s, int i) // 10 int add(int, int); // 11
The compiler, having read main
's statement, must now decide which
function must actually be called. It proceeds as follows:
This implies that at least the number of arguments must match the number of
parameters of the viable functions. Function 10's first argument is a
string
. As a string
cannot be initialized by an int
value no
appropriate conversion exists and function 10 is removed from the list of
candidate functions. double
parameters can be retained. Standard
conversions do exists for int
s to double
s, so all functions having
ordinary double
parameters can be retained. Therefore, the set of viable
functions consists of functions 1 through 9.
It may use any of the three standard template parameter transformation
procedures (cf. section 21.4) when trying to match actual types to
template type parameters. In this process it concludes that no type can be
determined for the T
in function 1's T &t1
parameter as the argument
3
is a constant int
value. Thus function 1 is removed from the list of
viable functions. The compiler is now confronted with the following set of
potentially instantiated function templates and ordinary functions:
void process(T1 [= int] const &t1, T2 [= int] const &t2); // 2 void process(T [= int] const &t, double d); // 3 void process(T [= int] const &t, int i); // 4 void process<int, int>(int i1, int i2); // 5 void process(int i1, int i2); // 6 void process(int i, double d); // 7 void process(double d, int i); // 8 void process(double d1, double d2); // 9
The compiler associates a direct match count value to each of the viable functions. The direct match count counts the number of arguments that can be matched to function parameters without an (automatic) type conversion. E.g., for function 2 this count equals 2, for function 7 it is 1 and for function 9 it is 0. The functions are now (decrementally) sorted by their direct match count values:
match count void process(T1 [= int] const &t1, T2 [= int] const &t2); 2 // 2 void process(T [= int] const &t, int i); 2 // 4 void process<int, int>(int i1, int i2); 2 // 5 void process(int i1, int i2); 2 // 6 void process(T [= int] const &t, double d); 1 // 3 void process(int i, double d); 1 // 7 void process(double d, int i); 1 // 8 void process(double d1, double d2); 0 // 9
If there is no draw for the top value the corresponding function is selected and the function selection process is completed.
When multiple functions appear at the top the compiler verifies that no ambiguity has been encountered. An ambiguity is encountered if the sequences of parameters for which type conversions were (not) required differ. As an example consider functions 3 and 8. Using D for `direct match' and C for `conversion' the arguments match function 3 as D,C and function 8 as C,D. Assuming that 2, 4, 5 and 6 were not available, then the compiler would have reported an ambiguity as the sequences of argument/parameter matching procedures differ for functions 3 and 8. The same difference is encountered comparing functions 7 and 8, but no such difference is encountered comparing functions 3 and 7.
At this point there is a draw for the top value and the compiler proceeds with the subset of associated functions (functions 2, 4, 5 and 6). With each of these functions an `ordinary parameter count' is associated counting the number of non-template parameters of the functions. The functions are decrementally sorted by this count, resulting in:
ordin. param. count void process(int i1, int i2); 2 // 6 void process(T [= int] const &t, int i); 1 // 4 void process(T1 [= int] const &t1, T2 [= int] const &t2); 0 // 2 void process<int, int>(int i1, int i2); 0 // 5
Now there is no draw for the top value. The corresponding function
(process(int, int)
, function 6) is selected and the function selection
process is completed. Function 6 is used in main
's function call
statement.
Had function 6 not been defined, function 4 would have been used. Assuming that neither function 4 nor function 6 had been defined, the selection process would continue with functions 2 and 5:
ordin. param. count void process(T1 [= int] const &t1, T2 [= int] const &t2); 0 // 2 void process<int, int>(int i1, int i2); 0 // 5
In this situation a draw is encountered once again and the selection process continues. A `type of function' value is associated with each of the functions having the highest ordinary parameter count and these functions are decrementally sorted by their type of function values. Value 2 is associated to ordinary functions, value 1 to template explicit specializations and value 0 to plain function templates.
If there is no draw for the top value the corresponding function is selected and the function selection process is completed. If there is a draw the compiler reports an ambiguity and cannot determine which function to call. Assuming only functions 2 and 5 existed then this selection step would have resulted in the following ordering:
function type void process<int, int>(int i1, int i2); 1 // 5 void process(T1 [= int] const &t1, T2 [= int] const &t2); 0 // 2
Function 5, the template explicit specialization, would have been selected.
Here is a summary of the function template selection mechanism (cf. figure Figure 27):struct Int { using type = int; };
Although at this point it may seem strange to embed a using declaration in a
struct, in chapter 23 we will encounter situations where this
is actually very useful. It allows us to define a variable of a type that is
required by the template. E.g., (ignore the use of typename
in the
following function parameter list, but see section 22.2.1 for
details):
template <typename Type> void func(typename Type::type value) { }
When calling func(10)
Int
has to be specified explicitly since
there may be many structs that define type
: the compiler needs some
assistance. The correct call is func<Int>(10)
. Now that it's clear that
Int
is meant, and the compiler correctly deduces that value
is an
int
.
But templates may be overloaded and our next definition is:
template <typename Type> void func(Type value) {}
Now, to call this function we specify func<int>(10)
and
again this flawlessly compiles.
But as we've seen in the previous section when the compiler determines which template to instantiate it creates a list of viable functions and selects the function to instantiate by matching the parameter types of viable functions with the provided actual argument types. To do so it has to determine the types of the parameters and herein lies a problem.
When evaluating Type = int
the compiler encounters the prototypes
func(int::type)
(first template definition) and func(int)
(second
template definition). But there is no int::type
, and so in a way this
generates an error. The error results from matching the provided template type
argument with the types used in the various template definitions.
A type-problem caused by substituting a type in a template definition is, however, not considered an error, but merely an indication that that particular type cannot be used in that particular template. The template is therefore removed from the list of candidate functions.
This principle is known as substitution failure is not an error (SFINAE) and it is often used by the compiler to select not only a simple overloaded function (as shown here) but also to choose among available template specializations (see also sections 23.6.1 and 23.9.3).
if (cond)
selection statement the if
constexpr (cond)
syntax is supported by the language. Although it can be used
in all situations where a standard if
selection statement are used, its
specific use is encountered inside function templates: if constexpr
allows
the compiler to (conditionally) instantiate elements of a template function,
depending on the compile-time evaluation of the if constexpr's (cond)
clause.
Here is an example:
1: void positive(); 2: void negative(); 3: 4: template <int value> 5: void fun() 6: { 7: if constexpr (value > 0) 8: positive(); 9: else if constexpr (value < 0) 10: negative(); 11: } 12: 13: int main() 14: { 15: fun<4>(); 16: }
if constexpr
statements start. Since value
is a template non-type parameter its value is compile-time available,
and so are the values of the condition sections.
fun<4>()
is called: the condition in line 7 is
therefore true
, and the condition in line 9 is false
.
fun<4>()
this way:
void fun<4>() { positive(); }
if constexpr
statements themselves do not result in
executable code: it is used by the compiler to select which part (or
parts) it should instantiate. In this case only positive
, which must be
available before the program's linking phase can properly complete.
Not every template declaration may be converted into a template definition. If a definition may be provided it is explicitly mentioned.
template <typename Type1, typename Type2> void function(Type1 const &t1, Type2 const &t2);
template void function<int, double>(int const &t1, double const &t2);
void (*fp)(double, double) = function<double, double>; void (*fp)(int, int) = function<int, int>;
template <> void function<char *, char *>(char *const &t1, char *const &t2);
friend void function<Type1, Type2>(parameters);
A variable template starts with a familiar template
header, followed by
the definition of the variable itself. The template header specifies a type,
for which a default type may also be specified. E.g.,
template<typename T = long double> constexpr T pi = T(3.1415926535897932385);
To use this variable a type must be specified, and as long as the initialized value can be converted to the specified type the conversion is silently performed by the compiler:
cout << pi<> << ' ' << pi<int>;
At the second insertion the long double
initialization value is
converted to int
, and 3 is displayed.
Specializations are also supported. E.g., to show the text `pi' a
specialization for a char const *
type can be defined:
template<> constexpr char const *pi<char const *> = "pi";
With this specialization we can do cout << pi<char const *>
to show
pi
.