File: overloading.yo

package info (click to toggle)
c%2B%2B-annotations 6.5.0-1
  • links: PTS
  • area: main
  • in suites: etch, etch-m68k
  • size: 15,176 kB
  • ctags: 2,567
  • sloc: cpp: 14,411; makefile: 2,988; ansic: 165; perl: 90; sh: 29
file content (217 lines) | stat: -rw-r--r-- 9,404 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
    Let's once again look at our tt(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:
        verb(
    int main()
    {
        add(2, add(3, 4));
    }
        )
    This is a perfectly acceptable solution for the occasional
situation. However, if we would have to add three entities regularly, an
em(overloaded) version of the tt(add()) function, expecting three arguments,
might be a useful thing to have. The solution for this problems is simple:
template functions may be overloaded.
        hi(overloading: template functions)

    To define an overloaded version, merely put multiple definitions of the
template in its definition header file. So, with the tt(add()) function this
would be something like:
        verb(
    template <typename Type>
    Type add(Type const &lvalue, Type const &rvalue)
    {
        return lvalue + rvalue;
    }

    template <typename Type>
    Type add(Type const &lvalue, Type const &mvalue, Type const &rvalue)
    {
        return lvalue + mvalue + rvalue;
    }
        )
    The overloaded function does not have to be defined in terms of simple
values. Like all overloaded functions, just a unique set of function
parameters is enough to define an overloaded version. For example, here's an
overloaded version that can be used to compute the sum of the elements of a
vector:
        verb(
    template <typename Type>
    Type add(std::vector<Type> const &vect)
    {
        return accumulate(vect.begin(), vect.end(), Type());
    }
        )

    Overloading templates does not have to restrict itself to the function's
parameter list. The template's type parameter list itself may also be
    hi(templates: overloading type parameter list)
overloaded. The last definition of the tt(add()) template allows us to specify
a tt(std::vector) as its first argument, but no tt(deque) or
tt(map). Overloaded versions for those types of containers could of course be
constructed, but where's the end to that? Instead, let's look for common
characteristics of these containers, and if found, define an overloaded
template function on these common characteristics. One common characteristic
of the mentioned containers is that they all support tt(begin()) and tt(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 data type it has
been 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 tt(add()) template:
        verb(
    template <typename Container, typename Type>
    Type add(Container const &cont, Type const &init)
    {
        return std::accumulate(cont.begin(), cont.end(), init);
    }
        )
    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() With the first statement, 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() With statement two, a single argument is used. Consequently, the
compiler will look for an overloaded version of tt(add()) requiring but one
argument. It finds the version 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() With statement three, the compiler again encounters an argument list
holding two arguments. However, the types of the arguments are different, so
it cannot use the tt(add()) template's first definition. 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 template function
        verb(
    add<std::vector<int>, int>(std::vector<int> const &, int const &)
        )
    )

    Having defined tt(add()) using two different template type parameters, and
a template function having a parameter list containing two parameters of these
types, we've exhausted the possibilities to define an tt(add()) function
template having a function parameter list showing two different types. Even
though the parameter types are different, we're still able to define a
template function tt(add()) as a template function merely returning the sum of
two differently typed entities:
        verb(
    template <typename T1, typename T2>
    T1 add(T1 const &lvalue, T2 const &rvalue)
    {
        return lvalue + rvalue;
    }
        )
    However, now we won't be able to instantiate tt(add()) using two
differently typed arguments anymore: the compiler won't be able resolve the
ambiguity. It cannot choose which of the two overloaded versions defining two
differently typed function parameters to use:
        verb(
    int main()
    {
        add(3, 4.5);
    }
    /*
        Compiler reports:

        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]
    */
        )
    Consider once again the overloaded function 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: e.g., three tt(int)s, three tt(double)s or
three tt(string)s. To remedy this, we define yet another overloaded version of
the function, this time accepting arguments of any type. Of course, when
calling this function we must make sure that tt(operator+()) is defined
between them, 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 these two overloaded versions, let's call tt(add())
as follows:
        centt(add(1, 2, 3);)
    In this case, one might expect the compiler to report an ambiguity. 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). However, the compiler reports no
ambiguity. The reason for this is the following: if an overloaded template
function is defined using em(more specialized) template type parameters (e.g.,
        hi(template functions: specialized type parameters) all equal types)
than another (overloaded) function, for which more general template type
parameters (e.g., all different) have been used, then the compiler will select
the more specialized function over the more general function wherever
possible.

    As a i(rule of thumb): when overloaded versions of a template function
are defined, each overloaded version must use a unique combination of
template type parameters to avoid ambiguities when the templates are
instantiated. Note that the em(ordering) of template type parameters in the
function's parameter list is not important. When trying to instantiate the
following tt(binarg()) template, an ambiguity will occur:
        verb(
    template <typename T1, typename T2>
    void binarg(T1 const &first, T2 const &second)
    {}
    // and:
    template <typename T1, typename T2>
    void binarg(T2 const &first, T1 const &second)  // exchange T1 and T2
    {}
        )
    The ambiguity should come as no surprise. After all, template type
parameters are just formal names. Their names (tt(T1), tt(T2) or tt(Whatever))
have no concrete meanings whatsoever.

    Finally, overloaded functions may be declared, either using plain
declarations or instantiation declarations, and explicit template parameter
types may also be used. For example:
    itemization(
    it() Declaring a template function tt(add()) accepting containers
of a certain type:
        verb(
    template <typename Container, typename Type>
    Type add(Container const &container, Type const &init);
        )
    it() The same function, but now using an instantiation declaration (note
that this requires that the compiler has already seen the template's
definition):
        verb(
    template int add<std::vector<int>, int>
                    (std::vector<int> const &vect, int const &init);
        )
    it() To disambiguate among multiple possibilities detected by the
compiler, explicit arguments may be used. For example:
        verb(
    std::vector<int> vi;
    int sum = add<std::vector<int>, int>(vi, 0);
        )
    )