-
Notifications
You must be signed in to change notification settings - Fork 17
/
operator.fqa
373 lines (274 loc) · 23.3 KB
/
operator.fqa
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
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
Operator overloading
{'section':13,'faq-page':'operator-overloading.html'}
This section is about operator overloading - a way to make the code "readable" as long as the reader doesn't care what the code actually does.
What's the deal with |operator| overloading?
FAQ: Overloaded operators provide an "intuitive interface" for the users of your class. They also allow templates
to work "equally well" with classes and built-in types.
The idea is to call functions using the syntax of C++ operators. Such functions can be defined to accept
parameters of user-defined types, giving the operators user-defined meaning. For example:
@
Matrix add(const Matrix& x, const Matrix& y);
Matrix operator+(const Matrix& x, const Matrix& y);
Matrix use_add(const Matrix& a, const Matrix& b, const Matrix& c)
{
return add(a,add(b,c));
}
Matrix use_plus(const Matrix& a, const Matrix& b, const Matrix& c)
{
return a + b + c;
}
@
FQA: Operator overloading provides strong source code encryption
(the time needed to figure out what |a+b| actually means is an exponential
function of the number of types, implicit conversions, template specializations and overloaded operator versions involved).
It is also one way to have [35.1 templates] malfunction equally miserably with user-defined and built-in types -
if |BraindeadPseudoTypeSafeIterator<T>| supports the syntax |*p| and |++p|, it can be used just like the built-in |T*| in a template "algorithm" like |std::for_each|.
However, it could also work without operator overloading - for example, you can have a global |increment| function
for all "iterator" types, and implement it for all pointer types using a global template wrapper calling |++p|.
Which means that operator overloading is pure syntactic sugar even if you don't consider templates a pile of toxic waste
you'd rather live without.
This syntactic sugar can only be added to a language like C++ together with more than a grain of salt. For example,
what if |a+b| fails? There's no good way to return an error status, because the language is "strongly typed",
so |a+b| always returns objects of the same type, for example, a |Matrix|. There's no natural "bad matrix value", and anyway
the whole point of |a+b| is that you can also write |a+b+c|. If you need to test each addition with an |if| statement,
|a+b| is not much better than |add(a,b)|. What about throwing an [17.1 exception] upon error? Not a bad idea, in a language
/supporting/ exceptions, as opposed to merely providing non-local |goto| using |throw\/try\/catch| syntax and having /you/
care about "exception safety" in each and every bit of code.
What about the allocation of our result - where does that |Matrix| live? If it's allocated dynamically with |new| and returned by reference,
who is supposed to [16.1 clean it up], in particular, who keeps the list of all temporaries created by |a+b+c...|? But if it's
returned by value, then the copy constructor is going to copy lots of matrices. Bad if you [10.9 care about performance].
And if you don't,
you /surely/ wouldn't mind the smaller run time overhead of /garbage collection/ (not to mention run time boundary checking and other kinds of safety belt),
so you'd probably choose a different language in the first place.
Operator overloading is not necessarily a bad idea - if you can actually keep the promise about "readability".
To do that, you need at least three things: comprehensible overload resolution rules, easy-to-use exceptions
and easy-to-use automatic memory management. C++ offers none of those.
-END
What are the benefits of operator overloading?
FAQ: You can "exploit the intuition" of the users of your classes. Their code will speak in the language of the problem
instead of the language of the machine. They'll learn faster and make less errors.
FQA: The keyword in the FAQ's answer is "exploit". Your intuition tells you that |a+b| just works - and in the "problem domain", you are right.
But in C++, it doesn't take the machine too long to [23.5 raise its ugly iron head] and start talking to you in its
barbaric language, and it has all the means to make you listen.
If you want to program "in the problem domain", use a language designed for that problem domain. Prototype numerical
algorithms in [http://www.mathworks.com/products/matlab/ Matlab] or the like, not in C++ with some matrix library. Design hardware in [http://www.verilog.com Verilog] or the like, not [http://www.systemc.org SystemC] (which is C++ with some hardware description primitives library).
The next best alternative is to use a general-purpose language where operator overloading and other metaprogramming features [13.1 actually work].
It is typically worse, but may be cheaper - special-purpose tools are used by less people than general-purpose ones,
so the vendor must charge each user a higher price.
And it will make you happy if you're one of those people who think that there's nothing a special-purpose language
can do that can't be done equally well or better using the wonderful metaprogramming facilities of the awesome language X
(typically /wrong/, but strong programmers can be very productive operating under this assumption - unless X=C++).
Operator overloading and other features sure make C++ equally adaptable to any problem domain. This is achieved
by making it the wrong tool for every job.
-END
What are some examples of operator overloading?
FAQ: Here are a few (some real, some hypothetical), there are many more.
`<ul>`
`<li>` |str1 + str2| concatenates a couple of |std::string| objects. `</li>`
`<li>` |NapoleonsBirthday++| increments an object of class |Date|. `</li>`
`<li>` |a*b| multiplies two |Number|s. `</li>`
`<li>` |a[i]| accesses the i'th element of a user-defined |Array| class object. `</li>`
`<li>` |x = *p| dereferences a "smart pointer" which actually represents an address of a disk record; the
dereferencing seeks to that location on the disk and fetches the record into |x|. `</li>`
`</ul>`
FQA: C++ operator overloading is a bad way to implement all of these things. It's an equally bad way to implement almost
everything that comes to mind.
`<ul>`
`<li>` /Strings/: a general purpose high-level language should have a good [6.2 built-in string type], because text processing is a very common task.
In particular, a string is an extremely
common type of function parameter, so if you have no built-in string type, each kind of interface will use its own kind
of string, and the users will have to spend most of their time converting between string types.
Even |char*| and |std::string|, which are both /standard/ string types (there are /lots and lots/ of non-standard ones),
have interactions that operator overloading fails to hide. For example, |dir + "\/" + file| compiles with |std::string dir|
and |char* file|, but fails to compile with |char* dir| and |std::string file|, [13.2 "exploiting"] the intuition of users. `</li>`
`<li>` /Dates/: what does |date++| /mean/? Does it add a day or a minute or a second? This question is especially interesting
if someone had a severe [15.1 modeling-the-universe-using-type-systems] attack and defined a |Time| class (incremented by seconds),
a |Date| class (incremented by days) and implicit conversions between them. And if you know that dates are incremented
in day intervals, so 1 means "one day", why not just use |int| instead of |Date|? [7.4 "Encapsulation"] - of what exactly? `</li>`
`<li>` /Numbers/: should be built into your language, or you should stop pretending that you're "programming in the
problem domain" (both approaches are perfectly legitimate). See the discussion about strings. `</li>`
`<li>` /Arrays/: should be built into the language, too. And if you use a tricky data structure because
resizable arrays built into your language are not good enough, making it look like an array is not necessarily a good idea. For
example, people might want to find the definition of the |operator[]| in |a[i]|. What should they do - search for "["?
Oh, they are using an IDE that actually understands 75% of C++ syntax? Now what - select the opening bracket and ask
for its definition? Never worked for me. `</li>`
`<li>` /Smart pointers to disk records/: I wish all C++ weenies used an operating system which implements file systems
using this advanced technique. This way, whenever the "dereferencing" would fail, either the error wouldn't be reported, or an [17.1 exception]
would be mishandled, and the weenies would lose their files, which would be good for everybody. `</li>`
`</ul>`
Basically most operator overloading use cases fall into one or more of the following categories:
`<ul>`
`<li>` the thing can't be implemented
well unless it's built into the language (arrays, strings, numbers, "smart pointers" doing things like reference counting)`</li>`
`<li>` the thing is tricky and shouldn't be confused with built-in types, so the interface should be different
("smart pointers" doing things like disk access, complicated data structures)`</li>`
`<li>` the thing is obscure and shouldn't be implemented at all
(incrementing dates by unclear amounts of time, [15.2 transferring] output streams into hexadecimal mode without an option to restore the previous mode)`</li>`
`</ul>`
However, it doesn't mean that operator overloading is never useful - that is, in the languages that can actually [13.1 support] it.
-END
But |operator| overloading makes my class look ugly; isn't it supposed to make my code clearer?
FAQ: No - it's supposed to make the code /using/ your class clearer! For example, you may claim that |T& operator[](int i)|
is less readable than |T& getAt(int i)|, but surely |arr[i]| is more readable than |arr.getAt(i)|!
You should realize that "in a reuse-oriented world", /usually/ (yes, the FAQ says "usually")
many people will use your class, but only one writes its code
(that one person is you). Think about the good of the majority - your users!
FQA: Those guys living in the "reuse-oriented" worlds are very noble characters. Too bad we're stuck on planet Earth,
which didn't seem to have any particular "orientation" last time I checked. On this planet, most classes you write
are used exclusively by yourself, the majority of the rest is used by two or three people, and once in a while
you get to write a class to be used by N people for N>3 - typically it's not an array class, but something with
/a little bit less trivial functionality/. Here on Earth, this is considered /good/: the more interaction
between people and components, the more errors there are likely to be (insert the FAQ's favorite pseudo-business-oriented statements about
"defect rate" here).
Of course this doesn't mean that /the other/ claims make any sense. For example, users have to figure out
what your class does before they use it, so its interface must be [10.18 defined in a readable way], not just look
readable when used. And your users will probably need to understand your implementation, too, because even
when your code no longer has bugs (this might take a while), bugs in other pieces of code may end up corrupting /your/ data
(this is C++), so people will have to [7.2 inspect] what's left of that data in order to find the original error.
So an unreadable and\/or complicated implementation is not a very good idea even in a "reuse-oriented" world.
Last but not least, |arr.getAt(i)| is /not that bad/.
-END
What operators can\/cannot be overloaded?
FAQ: Most can be overloaded. You can't overload the C operators |.| and |?:| (and |sizeof|). And you can't overload the
C++ operators |::| and |.*|. You can overload everything else.
FQA: There are other restrictions, for example there can't be a global |operator[]| overload - you can only overload
this operator using a class member function. Which is not necessarily bad.
-END
Can I overload |operator==| so it lets me compare two |char[]| using a string comparison?
FAQ: No, at least one parameter of an overloaded operator must be a user-defined type.
But even if you could do it, you shouldn't - you should use a class like |std::string|. Arrays are [6.15 evil]!
FQA: C++ doesn't let you do it for a good reason - if you could do it, how would you make sure that your
program is compiled consistently in the sense that your operator is called /everywhere/ - in all contexts
calling |operator==| on |char[]| arguments? And what if two such operators are defined by two different modules?
Unfortunately, these problems are almost equally severe with user-defined types. For example, |std::map| doesn't have
an overloaded |operator<<| printing it to an |ostream|. You can define one, but [15.10 so can I], and then our libraries
are linked together into a single program. The result is unpredictable, ranging from a link-time error to
a program printing some maps using your code and some with mine.
As to the "arrays are evil" mantras - how about chanting them to the authors of the C++ standard library that defined
an |std::ifstream| constructor accepting |const char*|, but not |std::string|? And they didn't define an |operator const char*|
in |std::string|, either. |std::ifstream in((std::string(dir) + "\/" + file).c_str());|. Give me a break.
And why does the FAQ say "a |std::string|-like class", not just "|std::string|"? Because if it did, the majority
of the audience stuck with one (or more) of the many, many "string-like" classes developed before one was added to the C++
standard library would laugh quite bitterly.
-END
Can I create a |operator**| for "to-the-power-of" operations?
FAQ: You can't. You can only overload operators already in C++, without changing the number of arguments, the associativity
or the precedence. If you think it's wrong, consider the problem of |a**p|, which means "a times the result of dereferencing p".
If you could define |operator**|, you'd make such expressions ambiguous.
And you know what? Operator overloading is just syntactic sugar. You can always define a function instead. Why not
overload |pow|?
Or you could overload |operator^|, which works but has the wrong precedence and associativity.
FQA: Sure, there's lots of grammar in C++, and making an unambiguous addition is
almost impossible. The fact is that, informally speaking, there's a finite amount of "good" syntax you can have in your language.
This is a valid argument if you advocate domain-specific languages (which can make the short, simple syntax map to stuff most useful in that domain),
or if you argue that "languages should have no syntax" (Lisp, Smalltalk and Forth are examples of languages with almost no syntax).
However, it's funny when this argument is used by the C++ FAQ. It is hardly compatible with the
[13.2 claims that C++ is applicable everywhere].
And then there's the /huge/ amount of /completely pointless/ complications in the C++ grammar
(like the constructor\/function declaration [10.19 puzzle]).
What's that? "Operator overloading doesn't provide anything fundamental, it's just syntactic sugar"? Now you're talking.
Make that "syntactic cyanide" and I'm in.
Overload |pow|? Can somebody tell what's the /point/ of all this overloading? If you want obfuscation, there are tools
you can use that allow you to keep the source in the unobfuscated form, which may be very handy at times. Besides,
isn't it |std::pow|? AFAIK you are not allowed to add names to the |std| namespace. And if you add a global function
pow, you'll lose the precious "transparency" (|std::pow(x,y)| will never call your version of |pow|, only |pow(x,y)| will),
so what's the point?
The idea to overload "bitwise exclusive or" to mean "power" is just stupid. I wonder where they get these ideas.
It's as if someone decided to overload "bitwise left shift" to mean "print to file". Wait a minute - they did that,
too... Oh well.
-END
Okay, that tells me the operators I /can/ override; which operators /should/ I override?
FAQ: You want to help your users, not confuse them. Overload operators if it makes the life of your users easier.
FQA: Translation: don't overload C++ operators. While we're at it, don't overload C++ functions, either. C++
overload resolution /always/ ends up confusing users. And you can't add error handling without using the horrible
C++ [17.1 exceptions], and you can't [16.1 allocate objects] simply and efficiently.
-END
What are some guidelines \/ "rules of thumb" for overloading operators?
FAQ: 22 (!!) "rules of thumb" are listed (actually 20, 2 just say "use common sense"). Basically, the rules are about
making overloaded operators behave similarly to built-in operators - if you define +, make it associative and
don't change the parameters, etc. The FAQ warns that the list is not exhaustive, nor should it be interpreted as
strict rules that can't have exceptions.
FQA: Rule of thumb #23: things which have simple functionality but are very hard to get right /linguistically/
should be in the compiler.
Use common sense. Do you have the time for all that thinking when all you get is "syntactic sugar" of questionable taste?
How about implementing some [7.2 user-visible functionality] instead? Not only is it what people (customers, employers, colleagues, friends)
ultimately care about, it's also much more fun.
-END
How do I create a subscript |operator| for a |Matrix| class?
FAQ: Use |operator()| which can get two indexes, i and j. Don't use |operator[]|, which can only get one index.
A code listing follows.
FQA: Oh, dear. You're writing a |Matrix| class. My condolences.
Once you define your subscript operator, be prepared to answer many more questions. How do you [16.1 allocate] the result
of |operator+|? How do you map expressions like |A+B*C| to optimized implementations of several operations (for example, |multiply_add|)?
Why are you writing a |Matrix| class with overloaded operators instead of prototyping the code in Matlab or the like
and then implementing the prototype in C so that it runs fast and anybody can tell how and why from the code,
instead of figuring out how |A+B*C| actually works?
-END
Why shouldn't my |Matrix| class's interface look like an array-of-array?
FAQ: The "array-of-array" alternative uses |operator[]| to return an array object, which in turn has an |operator[]|
returning a single element.
This approach is likely to be worse because of performance issues having to do with the optimal layout of the
matrix in memory, says the FAQ. Or maybe it says something else similar to it. It talks a lot about the issue.
FQA: You can't implement the array-of-array syntax with a single function call, because C++ has no |operator[][]|.
You need to return some temporary object with an |operator[]|.
This means extra work for you, because instead of writing a function you write a whole class and a function returning
its objects. This also means extra work for the user who has to read all these interfaces. This also means extra
work for the compiler. The chances of a compiler from planet Earth to optimize out the temporary objects are very close to zero.
Which means it's also extra work for the machine running your code.
And what did you achieve? "Syntactic sugar". This reasoning can frequently be used to optimize the entire |Matrix| class
with all its overloaded operators out of your schedule. And it doesn't depend on performance-related claims about memory layout and stuff.
-END
I still don't get it. Why shouldn't my |Matrix| class's interface look like an array-of-array?
FAQ: Because it makes checking the arguments or changing the data structure harder. The FAQ then explains how this
can still be done, and claims that the compiler will optimize the temporaries out, but doesn't like the fact that
it's a lot of work.
FQA: The FQA primarily doesn't like the fact that it's a lot of work. But it would also like to point out that:
1. The compiler will probably /not/ optimize the temporaries out. 2. Key performance-critical data structures are
extremely [16.17 costly] to change no matter how much "encapsulation" you use, because optimizations depend on the
representation. 3. Checking the arguments is /always/
hard with operator overloading, because what would |operator()| do once an error /was/ found - return -1 (how?), set a global error flag, throw
a C++ exception? All these options are not really acceptable.
Anyway, if you want to waste some time without getting useful work done, there are ways to do it that are much more fun than getting |operator[][]|
sort of work. And this is where the FAQ and the FQA agree.
-END
Should I design my classes from the outside (interfaces first) or from the inside (data first)?
FAQ: Outside, of course.
A long discussion about the accessors one should have in a |LinkedList| class follows. A ground-breaking conclusion
is reached: you should give accessors to the elements of the list, but not the "infrastructure" (say, the pointer to the first node).
FQA: /What?/ What does this question have to do with operator overloading? And what does the kind of accessors you
want to have in your class (the final code) have to do with the way you design it (think before you code)?
There's no single good way to design classes or any kind of software. Sometimes the interfaces are the important
thing, sometimes the implementation, and sometimes both. Designing is a kind of thinking. Sometimes you need to think
about the data in order to figure out what kind of interfaces are ultimately possible to implement efficiently.
Sometimes you must figure out the interface first to make sure the whole thing is actually useful. Frequently you have to think
about both and possibly iteratively refine them.
The idea that implementations are not important (because you can always [13.2 change] them or for other reasons)
may only emerge from implementing boringly trivial things, or from
doing nothing
(one way of doing nothing in the software industry is to obsessively seek the best way to spell the iteration over the elements of a list for a living).
By the way, the FAQ mentions "a billion-line app". C++ doesn't scale to many lines of code in the sense that a large monolithic
C++ application (one where all code runs in a single address space) can hardly be maintainable. This claim
may be made about other languages as well, but the problem is especially severe with languages assuming [6.5 unmanaged environments],
where a buffer overflow in module A may silently corrupt the data of module B. If you have lots of unmanaged code,
breaking it into processes can save several tons of your bacon (of course you have to build the system from the beginning that way - retro-fitting it is extremely expensive).
-END
How can I overload the prefix and postfix forms of operators |++| and |--|?
FAQ: Like this:
@
Iter& operator++ (); \/\/ ++p
Iter operator++ (int); \/\/ p++
@
The postfix version should return something equivalent to the copy of |*this| made before the operator call.
FQA: Silly syntax, isn't it? If you do overload operators, please read and follow all the boring rules about their
syntax and semantics. This will help you soften the blow on your users, or even fall asleep and forget about the
whole thing if we are lucky.
-END
Which is more efficient: |i++| or |++i|?
FAQ: It's the same for built-in types. |++i| may be faster for user-defined types, because there's no need to create
a copy that the compiler may fail to optimize out. Most likely the overhead is small, but why not pick the habit of
using |++i|?
FQA: Oh, suddenly the all-mighty compiler can't [13.12 optimize out a temporary]!
I actually picked that habit at some point. That was before I picked the even more useful habit of avoiding overloaded C++ operators.
The [13.10 really interesting questions] are about the performance of |A+B*C| compared to |D=B;D*=C;D+=A| compared to
an optimized |multiply_and_add_matrices(A,B,C)| function.
-END