mirror of
https://gitlab.com/fbb-git/cppannotations
synced 2024-11-18 10:06:54 +01:00
WIP on functiontemplates
git-svn-id: https://cppannotations.svn.sourceforge.net/svnroot/cppannotations/trunk@519 f6dd340e-d3f9-0310-b409-bdd246841980
This commit is contained in:
parent
2ec3a82712
commit
8da6f113a1
15 changed files with 479 additions and 443 deletions
|
@ -6,6 +6,9 @@ includefile(functiontemplates/intro)
|
|||
lsect(TEMPFUNDEF)(Defining function templates)
|
||||
includefile(functiontemplates/definitions)
|
||||
|
||||
subsect(Considerations regarding template parameters)
|
||||
includefile(functiontemplates/considerations)
|
||||
|
||||
subsect(Late-specified return type (C++0x))
|
||||
includefile(functiontemplates/alternate)
|
||||
|
||||
|
@ -39,12 +42,24 @@ includefile(functiontemplates/declarations)
|
|||
lsect(TEMPFUNINST)(Instantiating function templates)
|
||||
includefile(functiontemplates/instantiations)
|
||||
|
||||
subsect(Instantiations: no `code bloat')
|
||||
includefile(functiontemplates/inocodebloat)
|
||||
|
||||
lsect(TEMPFUNEXPLICIT)(Using explicit template types)
|
||||
includefile(functiontemplates/explicit)
|
||||
|
||||
sect(Overloading function templates)
|
||||
includefile(functiontemplates/overloading)
|
||||
|
||||
subsect(An example using overloaded function templates)
|
||||
includefile(functiontemplates/example)
|
||||
|
||||
subsect(Ambiguities when overloading function templates)
|
||||
includefile(functiontemplates/ambiguities)
|
||||
|
||||
subsect(Declaring overloaded function templates)
|
||||
includefile(functiontemplates/declaring)
|
||||
|
||||
lsect(SPECIALIZING)(Specializing templates for deviating types)
|
||||
includefile(functiontemplates/specialization)
|
||||
|
||||
|
|
80
yo/functiontemplates/ambiguities.yo
Normal file
80
yo/functiontemplates/ambiguities.yo
Normal file
|
@ -0,0 +1,80 @@
|
|||
Although it em(is) possible to define another function template tt(add) this
|
||||
will introduce an ambiguity as the compiler won't be able to choose which of
|
||||
the two overloaded versions defining two differently typed function parameters
|
||||
to use. For example when defining:
|
||||
verb(
|
||||
#include "add.h"
|
||||
|
||||
template <typename T1, typename T2>
|
||||
T1 add(T1 const &lvalue, T2 const &rvalue)
|
||||
{
|
||||
return lvalue + rvalue;
|
||||
}
|
||||
int main()
|
||||
{
|
||||
add(3, 4.5);
|
||||
}
|
||||
)
|
||||
the compiler will report an ambiguity like the following:
|
||||
verb(
|
||||
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:
|
||||
verb(
|
||||
template <typename Type>
|
||||
Type add(Type const &lvalue, Type const &mvalue, Type const &rvalue)
|
||||
{
|
||||
return lvalue + mvalue + rvalue;
|
||||
}
|
||||
)
|
||||
It may be considered as a disadvantage that only equally typed arguments
|
||||
are accepted by this function (three tt(int)s, three tt(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
|
||||
tt(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:
|
||||
verb(
|
||||
template <typename Type1, typename Type2, typename Type3>
|
||||
Type1 add(Type1 const &lvalue, Type2 const &mvalue, Type3 const &rvalue)
|
||||
{
|
||||
return lvalue + mvalue + rvalue;
|
||||
}
|
||||
)
|
||||
Now that we've defined the above two overloaded function templates
|
||||
expecting three arguments let's call tt(add) as follows:
|
||||
verb(
|
||||
add(1, 2, 3);
|
||||
)
|
||||
Should we expect an ambiguity here? After all, the compiler might select
|
||||
the former function, deducing that tt(Type == int), but it might also select
|
||||
the latter function, deducing that tt(Type1 == int, Type2 == int) and tt(Type3
|
||||
== int). Remarkably, the compiler reports no ambiguity.
|
||||
|
||||
No ambiguity is reported because of the following. If overloaded template
|
||||
functions are defined using em(less) and em(more) specialized template type
|
||||
parameters (e.g., less specialized: all types different vs. more specialized:
|
||||
hi(function templates: specialized type parameters) all types equal)
|
||||
then the compiler selects the more specialized function whenever possible.
|
||||
|
||||
As a i(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
|
||||
em(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:
|
||||
verb(
|
||||
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 (tt(T1), tt(T2) or tt(Whatever)) have no
|
||||
concrete meanings.
|
|
@ -1,7 +1,7 @@
|
|||
Although the em(construction) of class templates is the topic of chapter
|
||||
ref(TEMPCLASS) class templates have already extensively been em(used)
|
||||
earlier. For example, abstract containers (cf. chapter ref(CONTAINERS)) are
|
||||
defined as class templates. Class templates can, like
|
||||
ref(TEMPCLASS), we've already extensively em(used) class templates before. For
|
||||
example, abstract containers (cf. chapter ref(CONTAINERS)) are defined as
|
||||
class templates. Class templates can, like
|
||||
hi(ordinary class)hi(class: ordinary)
|
||||
ordinary classes, participate in the construction of class hierarchies.
|
||||
|
||||
|
@ -52,7 +52,7 @@ Caseless) could be constructed as follows:
|
|||
hi(transformation to a base class)
|
||||
hi(class template: transformation to a base class)
|
||||
em(transformation to a base class instantiated from a class template), the
|
||||
function template tt(sortVectors()) may now also be used to sort tt(Vector)
|
||||
function template tt(sortVector) may now also be used to sort tt(Vector)
|
||||
objects. For example:
|
||||
verb(
|
||||
int main()
|
||||
|
|
193
yo/functiontemplates/considerations.yo
Normal file
193
yo/functiontemplates/considerations.yo
Normal file
|
@ -0,0 +1,193 @@
|
|||
We've managed to design our first function template:
|
||||
verbinclude(examples/add.h)
|
||||
Look again at tt(add)'s parameters. By specifying tt(Type const &)
|
||||
rather than tt(Type) superfluous copying is prevented, at the same time
|
||||
allowing values of primitive types to be passed as arguments to the
|
||||
function. So, when tt(add(3, 4)) is called, tt(int(4)) will be assigned to
|
||||
tt(Type const &rvalue). In general, function parameters should be defined as
|
||||
tt(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
|
||||
tt(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):
|
||||
verb(
|
||||
int main()
|
||||
{
|
||||
size_t const &var = size_t(4);
|
||||
cout << add(var, var) << '\n';
|
||||
}
|
||||
)
|
||||
Here tt(var) is a reference to a constant tt(size_t). It is passed as
|
||||
argument to tt(add), thereby initializing tt(lvalue) and tt(rvalue) as tt(Type
|
||||
const &) to tt(size_t const &) values. The compiler interprets tt(Type) as
|
||||
tt(size_t). Alternatively, the parameters might have been specified using
|
||||
tt(Type &), rather than tt(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:
|
||||
verb(
|
||||
int main()
|
||||
{
|
||||
cout << add(string("a"), string("b")) << '\n';
|
||||
}
|
||||
)
|
||||
Here, a tt(string const &) cannot be used to initialize a tt(string &).
|
||||
Had tt(add) define tt(Type &&) parameters then the above program would have
|
||||
compiled just fine. In addition the following example correctly compiles as
|
||||
the compiler decides that tt(Type) apparently is a tt(string const):
|
||||
verb(
|
||||
int main()
|
||||
{
|
||||
string const &s = string("a");
|
||||
cout << add(s, s) << '\n';
|
||||
}
|
||||
)
|
||||
What can we deduce from these examples?
|
||||
itemization(
|
||||
it() In general, function parameters should be specified as tt(Type const
|
||||
&) parameters to prevent unnecessary copying.
|
||||
it() The template mechanism is fairly flexible. It will interpret
|
||||
formal types as plain types, const types, pointer types, etc., depending on
|
||||
the actually provided types. The i(rule of thumb) is that the formal type is
|
||||
used as a generic mask for the actual type, with the formal type name covering
|
||||
whatever part of the actual type must be covered. Some examples, assuming the
|
||||
parameter is defined as tt(Type const &):
|
||||
center(
|
||||
table(2)(ll)(
|
||||
rowline()
|
||||
row(
|
||||
cell(bf(Provided argument:)) cell(bf(Actually used Type:))
|
||||
)
|
||||
rowline()
|
||||
row(
|
||||
cell(tt(size_t const)) cell(tt(size_t))
|
||||
)
|
||||
row(
|
||||
cell(tt(size_t)) cell(tt(size_t))
|
||||
)
|
||||
row(
|
||||
cell(tt(size_t *)) cell(tt(size_t *))
|
||||
)
|
||||
row(
|
||||
cell(tt(size_t const *)) cell(tt(size_t const *))
|
||||
)
|
||||
rowline()
|
||||
)
|
||||
)
|
||||
)
|
||||
As a second example of a function template, consider the following
|
||||
function template:
|
||||
verb(
|
||||
template <typename Type, size_t Size>
|
||||
Type sum(Type const (&array)[Size])
|
||||
{
|
||||
Type tp = Type();
|
||||
|
||||
for (size_t idx = 0; idx < Size; idx++)
|
||||
tp += array[idx];
|
||||
|
||||
return tp;
|
||||
}
|
||||
)
|
||||
This template definition introduces the following new concepts and
|
||||
features:
|
||||
itemization(
|
||||
it() The emi(template parameter list). This template parameter list has
|
||||
two elements. The first element is a well-known template type parameter, but
|
||||
the second element has a very specific type: a tt(size_t). Template parameters
|
||||
of specific (i.e., non-formal) types used in template parameter lists are
|
||||
called
|
||||
hi(template non-type parameter)em(template non-type parameters).
|
||||
A template non-type parameter defines the type of a
|
||||
i(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
|
||||
tt(size_t).
|
||||
it() Looking at the function's head, we see one parameter:
|
||||
verb(
|
||||
Type const (&array)[Size]
|
||||
)
|
||||
This parameter defines tt(array) as a reference to an array having
|
||||
tt(Size) elements of type tt(Type) that may not be modified.
|
||||
it() In the parameter definition, both tt(Type) and tt(Size) are
|
||||
used. tt(Type) is of course the template's type parameter tt(Type), but
|
||||
tt(Size) is also a template parameter. It is a tt(size_t), whose value must be
|
||||
inferable by the compiler when it compiles an actual call of the tt(sum)
|
||||
function template. Consequently, tt(Size) must be a tt(const) value. Such a
|
||||
constant expression is called a em(template non-type parameter), and its type
|
||||
is named in the template's parameter list.
|
||||
it() When the function template is called, the compiler must be able to
|
||||
infer not only tt(Type)'s concrete value, but also tt(Size)'s value. Since
|
||||
the function tt(sum) only has one parameter, the compiler is only able to
|
||||
infer tt(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 tt(Type) elements. So, in the following tt(main) function
|
||||
the first statement will compile correctly but the second statement
|
||||
will not:
|
||||
verb(
|
||||
int main()
|
||||
{
|
||||
int values[5];
|
||||
int *ip = values;
|
||||
|
||||
cout << sum(values) << '\n'; // compiles OK
|
||||
cout << sum(ip) << '\n'; // won't compile
|
||||
}
|
||||
)
|
||||
it() Inside the function's body the statement tt(Type tp = Type()) is used
|
||||
to initialize tt(tp) to a default value. Note here that no fixed value (like
|
||||
0) is used. Any type's default value may be obtained using its default
|
||||
constructor
|
||||
hi(template type: initialization)
|
||||
rather than using a fixed numeric value. Of course, not every class
|
||||
accepts a numeric value as an argument to one of its constructors. But all
|
||||
types, even the primitive types, support default constructors (actually, some
|
||||
classes do not implement a default constructor, or make it inaccessible; but
|
||||
most do). The default constructor hi(constructor: primitive type) of primitive
|
||||
types will initialize their variables to 0 (or tt(false)). Furthermore, the
|
||||
statement tt(Type tp = Type()) is a true initialization: tt(tp) is initialized
|
||||
by tt(Type)'s default constructor, rather than using tt(Type)'s copy
|
||||
constructor to assign tt(Type)'s copy to tt(tp).
|
||||
|
||||
It's interesting to note here (although not directly related to the
|
||||
current topic) that the syntactic construction tt(Type tp(Type())) em(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
|
||||
tt(string s("hello")). Why, then, is tt(Type tp = Type()) accepted, whereas
|
||||
tt(Type tp(Type())) isn't? When tt(Type tp(Type())) is used it won't result
|
||||
in an error message. So we don't immediately detect that it's em(not) a
|
||||
tt(Type) object's default initialization. Instead, the compiler will start
|
||||
generating error messages once tt(tp) is used. This is caused by the fact that
|
||||
in bf(C++) (and in bf(C) alike) the compiler will do its best to recognize a
|
||||
function or
|
||||
hi(C++: function prevalence rule) function pointer whenever possible: the
|
||||
emi(function prevalence rule). According to this rule tt(Type()) is (because
|
||||
of the pair of parentheses) interpreted as a em(pointer to a function)
|
||||
expecting no arguments; returning a tt(Type). It will do so unless the
|
||||
compiler clearly is unable to do so. In the initialization tt(Type tp =
|
||||
Type()) it em(can't) see a pointer to a function as a tt(Type) object cannot
|
||||
be given the value of a function pointer (remember: tt(Type()) is interpreted
|
||||
as tt(Type (*)()) whenever possible). But in tt(Type tp(Type())) it em(can)
|
||||
use the pointer interpretation: tt(tp) is now em(declared) as a em(function)
|
||||
expecting a pointer to a function returning a tt(Type), with tt(tp) itself
|
||||
also returning a tt(Type). E.g., tt(tp) could have been defined as:
|
||||
verb(
|
||||
Type tp(Type (*funPtr)())
|
||||
{
|
||||
return (*funPtr)();
|
||||
}
|
||||
)
|
||||
it() Comparable to the first function template, tt(sum) also assumes the
|
||||
existence of certain public members in tt(Type)'s class. This time
|
||||
tt(operator+=) and tt(Type)'s copy constructor.
|
||||
)
|
||||
|
||||
Like class definitions, template definitions should not contain tt(using)
|
||||
hi(templates vs. using)hi(using vs. templates)
|
||||
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 tt(using) directives (E.g, a tt(cout) variable defined in the
|
||||
tt(std) namespace and in the programmer's own namespace). Instead, within
|
||||
template definitions only hi(fully qualified name)hi(name: fully qualified)
|
||||
fully qualified names, including all required namespace specifications should
|
||||
be used.
|
|
@ -12,18 +12,30 @@ deduced to be an tt(int), rather than an tt(int &&).
|
|||
This is fairly intuitive. But what happens if the actual type is tt(int &)?
|
||||
There is no such thing as an tt(int & &¶m) and so the compiler contracts
|
||||
the double reference by removing the rvalue reference, keeping the lvalue
|
||||
reference. Here a simple rule is followed: Any reference to an lvalue
|
||||
reference results in an lvalue reference and emi(vice versa): an lvalue to any
|
||||
reference also results in an lvalue:
|
||||
reference. Here the following rules are applied:
|
||||
|
||||
quote(
|
||||
1. A template function parameter defined as an lvalue reference (e.g.,
|
||||
tt(Type &)) receiving an lvalue reference argument results in a single
|
||||
lvalue reference.
|
||||
|
||||
2. A template function parameter defined as an rvalue reference (e.g.,
|
||||
tt(Type &&)) receiving any kind of reference argument uses the reference
|
||||
type of the argument.
|
||||
)
|
||||
Examples:
|
||||
itemization(
|
||||
it() if tt(Type) is tt(Actual &) then tt(Type &) is tt(Actual &) and
|
||||
tt(Type == Actual);
|
||||
it() if tt(Type) is tt(Actual &) then tt(Type &&) still is tt(Actual &)
|
||||
and tt(Type == Actual);
|
||||
it() if tt(Type) is tt(Actual &&) then tt(Type &) again is tt(Actual &)
|
||||
and tt(Type == Actual);
|
||||
it() if tt(Type) is tt(Actual &&) then tt(Type &&) is tt(Actual &&)
|
||||
and tt(Type == Actual);
|
||||
it() When providing an tt(Actual &) argument then tt(Type &) becomes an
|
||||
tt(Actual &) and tt(Type) is inferred as tt(Actual);
|
||||
|
||||
it() When providing an tt(Actual &) then tt(Type &&) becomes an
|
||||
tt(Actual &) and tt(Type) is inferred as tt(Actual);
|
||||
|
||||
it() When providing an tt(Actual &&) then tt(Type &) also becomes
|
||||
tt(Actual &) and tt(Type) is inferred as tt(Actual);
|
||||
|
||||
it() When providing an tt(Actual &&) then tt(Type &&) becomes tt(Actual
|
||||
&&) and tt(Type) is inferred as tt(Actual);
|
||||
)
|
||||
|
||||
Let's look at a concrete exampe where contraction occurs. Consider the
|
||||
|
@ -38,18 +50,18 @@ references to some template type parameter:
|
|||
)
|
||||
In this situation, when tt(function) is called with an (lvalue) argument of
|
||||
type tt(TP &) the template type parameter tt(Type) is deduced to be tt(Tp
|
||||
&). Therefore, tt(param) is considered to be instantiated as tt(Tp ¶m) (as
|
||||
tt(Tp & &¶m)) is contracted as tt(Tp ¶m). But with an rvalue typed
|
||||
argument (tt(Tp &&)) it results in tt(Tp &¶m) (again using contraction).
|
||||
&). Therefore, tt(Type &¶m) is instantiated as tt(Tp ¶m), tt(Type)
|
||||
becomes tt(Tp) and the rvalue reference is replaced by an lvalue reference.
|
||||
|
||||
Likewise, when tt(callee) is called using the tt(static_cast) the same
|
||||
contraction occurs, so if tt(Type &¶m) is interpreted as tt(Tp ¶m) the
|
||||
static cast em(also) uses type tt(Tp ¶m). If (Type &¶m) is interpreted
|
||||
as tt(Tp &¶m) the static cast em(also) uses type tt(Tp &¶m).
|
||||
contraction occurs, so tt(Type &¶m) operates on tt(Tp ¶m). Therefore
|
||||
(using contraction) the static cast em(also) uses type tt(Tp ¶m). If
|
||||
tt(param) happened to be of type tt(Tp &&) then the static cast uses type
|
||||
tt(Tp &¶m).
|
||||
|
||||
This characteristics allows us to pass a function argument straight through to
|
||||
This characteristic allows us to pass a function argument to
|
||||
a nested function em(without) changing its type: lvalues remain lvalues,
|
||||
rvalues remain rvalues. This characteristic is therefore also known as
|
||||
emi(perfect forwarding) which will be discussed in greated detail in section
|
||||
emi(perfect forwarding) which is discussed in greated detail in section
|
||||
ref(PERFECT). Perfect forwarding prevents the template author from having to
|
||||
define multiply overloaded versions of a function template.
|
||||
|
|
|
@ -18,7 +18,7 @@ computers keep getting faster and faster. What was a nuisance a few years ago
|
|||
is hardly noticeable today.
|
||||
it() Every time a function template is instantiated, its code appears in
|
||||
the resulting object module. However, if multiple instantiations of a template
|
||||
using the same actual types for its template parameter exist in multiple
|
||||
using the same actual types for its template parameters exist in multiple
|
||||
object files the emi(one definition rule) is lifted. The linker will weed out
|
||||
superfluous instantiations (i.e., identical definitions of instantiated
|
||||
templates). In the final program only one instantiation for a particular set
|
||||
|
|
21
yo/functiontemplates/declaring.yo
Normal file
21
yo/functiontemplates/declaring.yo
Normal file
|
@ -0,0 +1,21 @@
|
|||
Like any function, overloaded functions may be declared, either using
|
||||
plain declarations or instantiation declarations. Explicit template argument
|
||||
types may also be used. Example:
|
||||
itemization(
|
||||
it() To declare a function template tt(add) accepting certain containers:
|
||||
verb(
|
||||
template <typename Container, typename Type>
|
||||
Type add(Container const &container, Type const &init);
|
||||
)
|
||||
it() to use an instantiation declaration (in which case the compiler must
|
||||
already have seen the template's definition):
|
||||
verb(
|
||||
template int add<std::vector<int>, int>
|
||||
(std::vector<int> const &vect, int const &init);
|
||||
)
|
||||
it() to use explicit template type arguments:
|
||||
verb(
|
||||
std::vector<int> vi;
|
||||
int sum = add<std::vector<int>, int>(vi, 0);
|
||||
)
|
||||
)
|
|
@ -11,18 +11,17 @@ function tt(add) expects two tt(Type) arguments and returns their sum:
|
|||
return lvalue + rvalue;
|
||||
}
|
||||
)
|
||||
Note how closely the above function's definition follows its description:
|
||||
it gets two arguments, and returns its sum. Now consider what would happen if
|
||||
we would have defined this function for, e.g., tt(int) values. We would have
|
||||
written:
|
||||
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., tt(int) values. We would write:
|
||||
verb(
|
||||
int add(int const &lvalue, int const &rvalue)
|
||||
{
|
||||
return lvalue + rvalue;
|
||||
}
|
||||
)
|
||||
So far, so good. However, were we to add two doubles, we would have
|
||||
overloaded this function:
|
||||
So far, so good. However, were we to add two doubles, we would overload
|
||||
this function:
|
||||
verb(
|
||||
double add(double const &lvalue, double const &rvalue)
|
||||
{
|
||||
|
@ -37,10 +36,11 @@ basically the same function are required because of the strongly typed nature
|
|||
of bf(C++). Because of this, a truly generic function cannot be constructed
|
||||
without resorting to the i(template mechanism).
|
||||
|
||||
Fortunately, we've already seen a large part of a template function. Our
|
||||
initial function tt(add) actually is an implementation of such a function
|
||||
although it isn't a full template definition yet. If we would give the first
|
||||
tt(add) function to the compiler, it would produce an error message like:
|
||||
Fortunately, we've already seen an important part of a template
|
||||
function. Our initial function tt(add) actually is an implementation of such a
|
||||
function although it isn't a full template definition yet. If we gave the
|
||||
first tt(add) function to the compiler, it would produce an error message
|
||||
like:
|
||||
verb(
|
||||
error: `Type' was not declared in this scope
|
||||
error: parse error before `const'
|
||||
|
@ -52,7 +52,7 @@ em(formal) typename. Comparing it to the alternate implementations, it will be
|
|||
clear that we could have changed tt(Type) into tt(int) to get the first
|
||||
implementation, and into tt(double) to get the second.
|
||||
|
||||
The full template definition allows for this formal character of the
|
||||
The full template definition allows for this formal nature of the
|
||||
tt(Type) typename. Using the keyword tt(template), we prefix one line to
|
||||
our initial definition, obtaining the following function template
|
||||
definition:
|
||||
|
@ -124,191 +124,3 @@ available for streams.
|
|||
Within the template definition's scope formal type names overrule identically
|
||||
named identifiers of broader scopes.
|
||||
|
||||
Look again at the tt(add)'s parameters. By specifying tt(Type const &) rather
|
||||
than tt(Type) superfluous copying is prevented, at the same time allowing
|
||||
values of primitive types to be passed as arguments to the function. So, when
|
||||
tt(add(3, 4)) is called, tt(int(4)) will be assigned to tt(Type const
|
||||
&rvalue). In general, function parameters should be defined as tt(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 tt(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):
|
||||
verb(
|
||||
int main()
|
||||
{
|
||||
size_t const &var = size_t(4);
|
||||
cout << add(var, var) << '\n';
|
||||
}
|
||||
)
|
||||
Here tt(var) is a reference to a constant tt(size_t). It is passed as
|
||||
argument to tt(add), thereby initializing tt(lvalue) and tt(rvalue) as tt(Type
|
||||
const &) to tt(size_t const &) values. The compiler interpreting tt(Type) as
|
||||
tt(size_t). Alternatively, the parameters might have been specified using
|
||||
tt(Type &), rather than tt(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:
|
||||
verb(
|
||||
int main()
|
||||
{
|
||||
cout << add(string("a"), string("b")) << '\n';
|
||||
}
|
||||
)
|
||||
Here, a tt(string const &) cannot be used to initialize a tt(string &).
|
||||
If tt(add) would define tt(Type &&) parameters the above program em(would)
|
||||
compile just fine. In addition the following will compile as the compiler
|
||||
decides that tt(Type) apparently is a tt(string const):
|
||||
verb(
|
||||
int main()
|
||||
{
|
||||
string const &s = string("a");
|
||||
cout << add(s, s) << '\n';
|
||||
}
|
||||
)
|
||||
What can we deduce from these examples?
|
||||
itemization(
|
||||
it() In general, function parameters should be specified as tt(Type const
|
||||
&) parameters to prevent unnecessary copying.
|
||||
it() The template mechanism is fairly flexible. It will interpret
|
||||
formal types as plain types, const types, pointer types, etc., depending on
|
||||
the actually provided types. The i(rule of thumb) is that the formal type is
|
||||
used as a generic mask for the actual type, with the formal type name covering
|
||||
whatever part of the actual type must be covered. Some examples, assuming the
|
||||
parameter is defined as tt(Type const &):
|
||||
center(
|
||||
table(2)(ll)(
|
||||
row(
|
||||
cell(bf(argument type)) cell(bf(Type ==))
|
||||
)
|
||||
row(
|
||||
cell(tt(size_t const)) cell(tt(size_t))
|
||||
)
|
||||
row(
|
||||
cell(tt(size_t)) cell(tt(size_t))
|
||||
)
|
||||
row(
|
||||
cell(tt(size_t *)) cell(tt(size_t *))
|
||||
)
|
||||
row(
|
||||
cell(tt(size_t const *)) cell(tt(size_t const *))
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
As a second example of a function template, consider the following
|
||||
function template:
|
||||
verb(
|
||||
template <typename Type, size_t Size>
|
||||
Type sum(Type const (&array)[Size])
|
||||
{
|
||||
Type tp = Type();
|
||||
|
||||
for (size_t idx = 0; idx < Size; idx++)
|
||||
tp += array[idx];
|
||||
|
||||
return tp;
|
||||
}
|
||||
)
|
||||
This template definition introduces the following new concepts and
|
||||
features:
|
||||
itemization(
|
||||
it() The emi(template parameter list). This template parameter list has
|
||||
two elements. The first element is a well-known template type parameter, but
|
||||
the second element has a very specific type: a tt(size_t). Template parameters
|
||||
of specific (i.e., non-formal) types used in template parameter lists are
|
||||
called
|
||||
hi(template non-type parameter)em(template non-type parameters).
|
||||
A template non-type parameter defines the type of a
|
||||
i(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
|
||||
tt(size_t).
|
||||
it() Looking at the function's head, we see one parameter:
|
||||
verb(
|
||||
Type const (&array)[Size]
|
||||
)
|
||||
This parameter defines tt(array) as a reference to an array having
|
||||
tt(Size) elements of type tt(Type) that may not be modified.
|
||||
it() In the parameter definition, both tt(Type) and tt(Size) are
|
||||
used. tt(Type) is of course the template's type parameter tt(Type), but
|
||||
tt(Size) is also a template parameter. It is a tt(size_t), whose value must be
|
||||
inferable by the compiler when it compiles an actual call of the tt(sum)
|
||||
function template. Consequently, tt(Size) must be a tt(const) value. Such a
|
||||
constant expression is called a em(template non-type parameter), and its type
|
||||
is named in the template's parameter list.
|
||||
it() When the function template is called, the compiler must be able to
|
||||
infer not only tt(Type)'s concrete value, but also tt(Size)'s value. Since
|
||||
the function tt(sum) only has one parameter, the compiler is only able to
|
||||
infer tt(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 tt(Type) elements. So, in the following tt(main) function
|
||||
the first statement will compile correctly but the second statement
|
||||
will not:
|
||||
verb(
|
||||
int main()
|
||||
{
|
||||
int values[5];
|
||||
int *ip = values;
|
||||
|
||||
cout << sum(values) << '\n'; // compiles OK
|
||||
cout << sum(ip) << '\n'; // won't compile
|
||||
}
|
||||
)
|
||||
it() Inside the function's body the statement tt(Type tp = Type()) is used
|
||||
to initialize tt(tp) to a default value. Note here that no fixed value (like
|
||||
0) is used. Any type's default value may be obtained using its default
|
||||
constructor
|
||||
hi(template type: initialization)
|
||||
rather than using a fixed numeric value. Of course, not every class
|
||||
accepts a numeric value as an argument to one of its constructors. But all
|
||||
types, even the primitive types, support default constructors (actually, some
|
||||
classes do not implement a default constructor, or make it inaccessible; but
|
||||
most do). The default constructor hi(constructor: primitive type) of primitive
|
||||
types will initialize their variables to 0 (or tt(false)). Furthermore, the
|
||||
statement tt(Type tp = Type()) is a true initialization: tt(tp) is initialized
|
||||
by tt(Type)'s default constructor, rather than using tt(Type)'s copy
|
||||
constructor to assign tt(Type)'s copy to tt(tp).
|
||||
|
||||
It's interesting to note here (although not directly related to the
|
||||
current topic) that the syntactic construction tt(Type tp(Type())) em(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
|
||||
tt(string s("hello")). Why, then, is tt(Type tp = Type()) accepted, whereas
|
||||
tt(Type tp(Type())) isn't? When tt(Type tp(Type())) is used it won't result
|
||||
in an error message. So we don't immediately detect that it's em(not) a
|
||||
tt(Type) object's default initialization. Instead, the compiler will start
|
||||
generating error messages once tt(tp) is used. This is caused by the fact that
|
||||
in bf(C++) (and in bf(C) alike) the compiler will do its best to recognize a
|
||||
function or
|
||||
hi(C++: function prevalence rule) function pointer whenever possible: the
|
||||
emi(function prevalence rule). According to this rule tt(Type()) is (because
|
||||
of the pair of parentheses) interpreted as a em(pointer to a function)
|
||||
expecting no arguments; returning a tt(Type). It will do so unless the
|
||||
compiler clearly is unable to do so. In the initialization tt(Type tp =
|
||||
Type()) it em(can't) see a pointer to a function as a tt(Type) object cannot
|
||||
be given the value of a function pointer (remember: tt(Type()) is interpreted
|
||||
as tt(Type (*)()) whenever possible). But in tt(Type tp(Type())) it em(can)
|
||||
use the pointer interpretation: tt(tp) is now em(declared) as a em(function)
|
||||
expecting a pointer to a function returning a tt(Type), with tt(t) itself also
|
||||
returning a tt(Type). E.g., tt(t) could have been defined as:
|
||||
verb(
|
||||
Type t(Type (*tp)())
|
||||
{
|
||||
return (*tp)();
|
||||
}
|
||||
)
|
||||
it() Comparable to the first function template, tt(sum) also assumes the
|
||||
existence of certain public members in tt(Type)'s class. This time
|
||||
tt(operator+=) and tt(Type)'s copy constructor.
|
||||
)
|
||||
|
||||
Like class definitions, template definitions should not contain tt(using)
|
||||
hi(templates vs. using)hi(using vs. templates)
|
||||
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 tt(using) directives (E.g, a tt(cout) variable defined in the
|
||||
tt(std) namespace and in the programmer's own namespace). Instead, within
|
||||
template definitions only hi(fully qualified name)hi(name: fully qualified)
|
||||
fully qualified names, including all required namespace specifications should
|
||||
be used.
|
||||
|
|
38
yo/functiontemplates/example.yo
Normal file
38
yo/functiontemplates/example.yo
Normal file
|
@ -0,0 +1,38 @@
|
|||
With all these overloaded versions in place, we may now start the compiler
|
||||
to compile the following function:
|
||||
verb(
|
||||
using namespace std;
|
||||
|
||||
int main()
|
||||
{
|
||||
vector<int> v;
|
||||
|
||||
add(3, 4); // 1 (see text)
|
||||
add(v); // 2
|
||||
add(v, 0); // 3
|
||||
}
|
||||
)
|
||||
itemization(
|
||||
it() In statement 1 the compiler recognizes two identical types, both
|
||||
tt(int). It will therefore instantiate tt(add<int>), our very first definition
|
||||
of the tt(add) template.
|
||||
it() In statement 2 a single argument is used. Consequently, the compiler
|
||||
looks for an overloaded version of tt(add) requiring but one argument. It
|
||||
finds the overloaded function template expecting a tt(std::vector), deducing
|
||||
that the template's type parameter must be tt(int). It instantiates
|
||||
verb(
|
||||
add<int>(std::vector<int> const &)
|
||||
)
|
||||
it() In statement 3 the compiler again encounters an argument list having
|
||||
two arguments. However, this time the types of the arguments aren't equal,
|
||||
so tt(add) template's first definition can't be used. But it em(can) use the
|
||||
last definition, expecting entities having different types. As a
|
||||
tt(std::vector) supports tt(begin) and tt(end), the compiler is now able to
|
||||
instantiate the function template
|
||||
verb(
|
||||
add<std::vector<int>, int>(std::vector<int> const &, int const &)
|
||||
)
|
||||
)
|
||||
Having defined the tt(add) function template for two equal and two
|
||||
different template type parameters we've exhausted the possibilities for using
|
||||
an tt(add) function template having two template type parameters.
|
11
yo/functiontemplates/examples/lvalue.cc
Normal file
11
yo/functiontemplates/examples/lvalue.cc
Normal file
|
@ -0,0 +1,11 @@
|
|||
#include <cmath>
|
||||
|
||||
template<typename Type>
|
||||
void call(Type (*fp)(Type), Type const &value)
|
||||
{
|
||||
(*fp)(value);
|
||||
}
|
||||
int main()
|
||||
{
|
||||
call(sqrt, 2.0);
|
||||
}
|
|
@ -45,51 +45,3 @@ alternatives, all of them possible and available):
|
|||
But it's good practice to avoid type casts wherever possible. How to do
|
||||
this is explained in the next section (ref(TEMPFUNEXPLICIT)).
|
||||
|
||||
As mentioned in section ref(TEMPFUNDECL), the linker removes
|
||||
hi(linker: removing identical template instantiations) identical
|
||||
instantiations of a template from the final program, leaving only one
|
||||
instantiation for each unique set of actual template type parameters. To
|
||||
illustrate the linker's behavior we do as follows:
|
||||
itemization(
|
||||
it() First we construct several source files:
|
||||
itemization(
|
||||
itt(source1.cc) defines a function tt(fun), instantiating
|
||||
tt(add) for tt(int)-type arguments, including tt(add)'s template
|
||||
definition. It displays tt(add)'s address:
|
||||
verbinclude(examples/pointerunion.h)
|
||||
verbinclude(examples/source1.cc)
|
||||
itt(source2.cc) defines the same function, but merely declares the
|
||||
proper tt(add) template using a template declaration (em(not) an instantiation
|
||||
declaration). Here is tt(source2.cc):
|
||||
verbinclude(examples/source2.cc)
|
||||
itt(main.cc) again includes tt(add)'s template definition,
|
||||
declares the function tt(fun) and defines tt(main), defining tt(add)
|
||||
for tt(int)-type arguments as well and displaying tt(add)'s function
|
||||
address. It also calls the function tt(fun). Here is tt(main.cc):
|
||||
verbinclude(examples/main.cc)
|
||||
)
|
||||
it() All sources are compiled to object modules. Note the different sizes
|
||||
of tt(source1.o) (1912 bytes using tt(g++) version 4.3.4. All sizes reported
|
||||
here may differ somewhat for different compilers and/or run-time libraries)
|
||||
and tt(source2.o) (1740 bytes). Since tt(source1.o) contains the instantiation
|
||||
of tt(add), it is somewhat larger than tt(source2.o), containing only the
|
||||
template's declaration. Now we're ready to start our little experiment.
|
||||
it() Linking tt(main.o) and tt(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:
|
||||
verb(
|
||||
0x80486d8
|
||||
0x80486d8
|
||||
)
|
||||
Furthermore, the size of the resulting program is 6352 bytes.
|
||||
it() Linking tt(main.o) and tt(source2.o), we now link together an object
|
||||
module containing the instantiation of the tt(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.
|
||||
)
|
||||
From our little experiment we conclude that the linker indeed removes
|
||||
identical template instantiations from a final program. Furthermore we
|
||||
conclude that using mere template declarations does not result in template
|
||||
instantiations.
|
||||
|
|
|
@ -18,7 +18,7 @@ and emi(function template) are introduced and several examples of
|
|||
templates are provided (both in this chapter and in chapter ref(CONCRETE)).
|
||||
Template em(classes) are covered in chapter ref(TEMPCLASS).
|
||||
|
||||
Templates offered standard by the language include the abstract containers
|
||||
Templates already offered by the language include the abstract containers
|
||||
(cf. chapter ref(CONTAINERS)); the tt(string) (cf. chapter ref(String));
|
||||
streams (cf. chapter ref(IOStreams)); and the generic algorithms (cf. chapter
|
||||
ref(GENERIC)). So, templates play a central role in present-day bf(C++), and
|
||||
|
@ -36,4 +36,4 @@ classes that are not templates), but on constructing templates.
|
|||
|
||||
This chapter starts by introducing em(function templates). The emphasis is on
|
||||
the required syntax. This chapter lays the foundation upon which the other
|
||||
chapter about templates are built.
|
||||
chapters about templates are built.
|
||||
|
|
|
@ -49,32 +49,24 @@ 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
|
||||
i(function-to-pointer transformation). For example:
|
||||
verb(
|
||||
#include <cmath>
|
||||
|
||||
template<typename Type>
|
||||
void call(Type (*fp)(Type), Type const &value)
|
||||
{
|
||||
(*fp)(value);
|
||||
}
|
||||
int main()
|
||||
{
|
||||
call(&sqrt, 2.0);
|
||||
}
|
||||
)
|
||||
verbinclude(examples/lvalue.cc)
|
||||
In this example, the address of the tt(sqrt) function is passed to
|
||||
tt(call), expecting a pointer to a function returning a tt(Type) and expecting
|
||||
a tt(Type) for its argument. Using the function-to-pointer transformation,
|
||||
tt(sqrt)'s address is assigned to tt(fp), deducing that tt(Type) is tt(double)
|
||||
in the process. The argument tt(2.0) could not have been specified as tt(2) as
|
||||
there is no tt(int sqrt(int)) prototype. Furthermore, the function's first
|
||||
parameter specifies tt(Type (*fp)(Type)), rather than tt(Type (*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, tt(fp)'s argument tt(Type) is not a function template
|
||||
parameter, but a parameter of the function tt(fp) points to. Since tt(sqrt)
|
||||
has prototype tt(double sqrt(double)), rather than tt(double sqrt(double const
|
||||
&)), tt(call)'s parameter tt(fp) em(must) be specified as tt(Type
|
||||
in the process (note that tt(sqrt) is the em(address) of a function, not a
|
||||
variable that is a pointer to a function, hence the lvalue
|
||||
transformation).
|
||||
|
||||
The argument tt(2.0) could not have been specified as tt(2) as there is no
|
||||
tt(int sqrt(int)) prototype. Furthermore, the function's first parameter
|
||||
specifies tt(Type (*fp)(Type)), rather than tt(Type (*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, tt(fp)'s argument tt(Type) is not a function template parameter, but
|
||||
a parameter of the function tt(fp) points to. Since tt(sqrt) has prototype
|
||||
tt(double sqrt(double)), rather than tt(double sqrt(double const &)),
|
||||
tt(call)'s parameter tt(fp) em(must) be specified as tt(Type
|
||||
(*fp)(Type)). It's that strict.
|
||||
)
|
||||
)
|
||||
|
|
49
yo/functiontemplates/nocodebloat.yo
Normal file
49
yo/functiontemplates/nocodebloat.yo
Normal file
|
@ -0,0 +1,49 @@
|
|||
As mentioned in section ref(TEMPFUNDECL), the linker removes
|
||||
hi(linker: removing identical template instantiations) identical
|
||||
instantiations of a template from the final program, leaving only one
|
||||
instantiation for each unique set of actual template type parameters. To
|
||||
illustrate the linker's behavior we do as follows:
|
||||
itemization(
|
||||
it() First we construct several source files:
|
||||
itemization(
|
||||
itt(source1.cc) defines a function tt(fun), instantiating
|
||||
tt(add) for tt(int)-type arguments, including tt(add)'s template
|
||||
definition. It displays tt(add)'s address:
|
||||
verbinclude(examples/pointerunion.h)
|
||||
verbinclude(examples/source1.cc)
|
||||
itt(source2.cc) defines the same function, but merely declares the
|
||||
proper tt(add) template using a template declaration (em(not) an instantiation
|
||||
declaration). Here is tt(source2.cc):
|
||||
verbinclude(examples/source2.cc)
|
||||
itt(main.cc) again includes tt(add)'s template definition,
|
||||
declares the function tt(fun) and defines tt(main), defining tt(add)
|
||||
for tt(int)-type arguments as well and displaying tt(add)'s function
|
||||
address. It also calls the function tt(fun). Here is tt(main.cc):
|
||||
verbinclude(examples/main.cc)
|
||||
)
|
||||
it() All sources are compiled to object modules. Note the different sizes
|
||||
of tt(source1.o) (1912 bytes using tt(g++) version 4.3.4 (sizes of object
|
||||
modules reported in this section may differ for different compilers and/or
|
||||
run-time libraries)) and tt(source2.o) (1740 bytes). Since tt(source1.o)
|
||||
contains the instantiation of tt(add), it is somewhat larger than
|
||||
tt(source2.o), containing only the template's declaration. Now we're ready to
|
||||
start our little experiment.
|
||||
it() Linking tt(main.o) and tt(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:
|
||||
verb(
|
||||
0x80486d8
|
||||
0x80486d8
|
||||
)
|
||||
Furthermore, the size of the resulting program is 6352 bytes.
|
||||
it() Linking tt(main.o) and tt(source2.o), we now link together an object
|
||||
module containing the instantiation of the tt(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.
|
||||
)
|
||||
From our little experiment we conclude that the linker indeed removes
|
||||
identical template instantiations from a final program. Furthermore we
|
||||
conclude that using mere template declarations does not result in template
|
||||
instantiations.
|
|
@ -42,6 +42,9 @@ vector:
|
|||
return accumulate(vect.begin(), vect.end(), Type());
|
||||
}
|
||||
)
|
||||
|
||||
COMMENT(OVERLOADING FUNCTION TEMPLATE PARAMETERS)
|
||||
|
||||
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
|
||||
|
@ -68,9 +71,9 @@ overloaded version of the tt(add) template:
|
|||
}
|
||||
)
|
||||
One may wonder whether the tt(init) parameter could not be left out of the
|
||||
parameter list as tt(init) will often have a default initialization value. The
|
||||
answer is a somewhat complex `yes', It em(is) possible to define the tt(add)
|
||||
function as follows:
|
||||
parameter list as tt(init) often has a default initialization value. The
|
||||
answer is `yes', but there are complications. It em(is) possible to define the
|
||||
tt(add) function as follows:
|
||||
verb(
|
||||
template <typename Type, typename Container>
|
||||
Type add(Container const &cont)
|
||||
|
@ -78,9 +81,9 @@ function as follows:
|
|||
return std::accumulate(cont.begin(), cont.end(), Type());
|
||||
}
|
||||
)
|
||||
But note that the template's type parameters were reordered, which is
|
||||
necessary because the compiler won't be able to determine tt(Type) in a call
|
||||
like:
|
||||
Note, however, that the template's type parameters were reordered, which
|
||||
is necessary because the compiler won't be able to determine tt(Type) in a
|
||||
call like:
|
||||
verb(
|
||||
int x = add(vectorOfInts);
|
||||
)
|
||||
|
@ -97,145 +100,3 @@ template parameter exists, a em(template template parameter), allowing the
|
|||
compiler to determine tt(Type) directly from the actual container
|
||||
argument. Template template parameters are discussed in section
|
||||
ref(TEMPTEMPPAR).
|
||||
|
||||
With all these overloaded versions in place, we may now start the compiler
|
||||
to compile the following function:
|
||||
verb(
|
||||
using namespace std;
|
||||
|
||||
int main()
|
||||
{
|
||||
vector<int> v;
|
||||
|
||||
add(3, 4); // 1 (see text)
|
||||
add(v); // 2
|
||||
add(v, 0); // 3
|
||||
}
|
||||
)
|
||||
itemization(
|
||||
it() In statement 1 the compiler recognizes two identical types, both
|
||||
tt(int). It will therefore instantiate tt(add<int>), our very first definition
|
||||
of the tt(add) template.
|
||||
it() In statement 2 a single argument is used. Consequently, the compiler
|
||||
looks for an overloaded version of tt(add) requiring but one argument. It
|
||||
finds the overloaded function template expecting a tt(std::vector), deducing
|
||||
that the template's type parameter must be tt(int). It instantiates
|
||||
verb(
|
||||
add<int>(std::vector<int> const &)
|
||||
)
|
||||
it() In statement 3 the compiler again encounters an argument list having
|
||||
two arguments. However, this time the types of the arguments aren't equal,
|
||||
so tt(add) template's first definition can't be used. But it em(can) use the
|
||||
last definition, expecting entities having different types. As a
|
||||
tt(std::vector) supports tt(begin) and tt(end), the compiler is now able to
|
||||
instantiate the function template
|
||||
verb(
|
||||
add<std::vector<int>, int>(std::vector<int> const &, int const &)
|
||||
)
|
||||
)
|
||||
Having defined the tt(add) function template for two equal and two
|
||||
different template type parameters we've exhausted the possibilities for using
|
||||
an tt(add) function template having two template type parameters.
|
||||
|
||||
Although it em(is) possible to define another function template tt(add) this
|
||||
will introduce an ambiguity as the compiler won't be able to choose which of
|
||||
the two overloaded versions defining two differently typed function parameters
|
||||
to use. For example when defining:
|
||||
verb(
|
||||
#include "add.h"
|
||||
|
||||
template <typename T1, typename T2>
|
||||
T1 add(T1 const &lvalue, T2 const &rvalue)
|
||||
{
|
||||
return lvalue + rvalue;
|
||||
}
|
||||
int main()
|
||||
{
|
||||
add(3, 4.5);
|
||||
}
|
||||
)
|
||||
the compiler will report an ambiguity like the following:
|
||||
verb(
|
||||
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:
|
||||
verb(
|
||||
template <typename Type>
|
||||
Type add(Type const &lvalue, Type const &mvalue, Type const &rvalue)
|
||||
{
|
||||
return lvalue + mvalue + rvalue;
|
||||
}
|
||||
)
|
||||
It may be considered as a disadvantage that only equally typed arguments
|
||||
are accepted by this function (three tt(int)s, three tt(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
|
||||
tt(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:
|
||||
verb(
|
||||
template <typename Type1, typename Type2, typename Type3>
|
||||
Type1 add(Type1 const &lvalue, Type2 const &mvalue, Type3 const &rvalue)
|
||||
{
|
||||
return lvalue + mvalue + rvalue;
|
||||
}
|
||||
)
|
||||
Now that we've defined the above two overloaded function templates
|
||||
expecting three arguments let's call tt(add) as follows:
|
||||
verb(
|
||||
add(1, 2, 3);
|
||||
)
|
||||
Should we expect an ambiguity here? After all, the compiler might select
|
||||
the former function, deducing that tt(Type == int), but it might also select
|
||||
the latter function, deducing that tt(Type1 == int, Type2 == int) and tt(Type3
|
||||
== int). Remarkably, the compiler reports no ambiguity.
|
||||
|
||||
No ambiguity is reported because of the following. If overloaded template
|
||||
functions are defined using em(less) and em(more) specialized template type
|
||||
parameters (e.g., less specialized: all types different vs. more specialized:
|
||||
hi(function templates: specialized type parameters) all types equal)
|
||||
then the compiler selects the more specialized function whenever possible.
|
||||
|
||||
As a i(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
|
||||
em(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:
|
||||
verb(
|
||||
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 (tt(T1), tt(T2) or tt(Whatever)) have no
|
||||
concrete meanings.
|
||||
|
||||
Like any function overloaded functions may also be declared, either using
|
||||
plain declarations or instantiation declarations. Explicit template argument
|
||||
types may also be used. Example:
|
||||
itemization(
|
||||
it() To declare a function template tt(add) accepting certain containers:
|
||||
verb(
|
||||
template <typename Container, typename Type>
|
||||
Type add(Container const &container, Type const &init);
|
||||
)
|
||||
it() to use an instantiation declaration (in which case the compiler must
|
||||
already have seen the template's definition):
|
||||
verb(
|
||||
template int add<std::vector<int>, int>
|
||||
(std::vector<int> const &vect, int const &init);
|
||||
)
|
||||
it() to use explicit template type arguments:
|
||||
verb(
|
||||
std::vector<int> vi;
|
||||
int sum = add<std::vector<int>, int>(vi, 0);
|
||||
)
|
||||
)
|
||||
|
|
Loading…
Reference in a new issue