- I'm Dan Saks and there's my topic.
Let's just do it.
This is really, the whole thing is focused
on a particular use of the keyword friend.
It's an interesting little corner case.
What we're really going to do
is show you the technique,
but what I think is really interesting
is all the little bits and pieces
that go into the back story.
The interest is in the details,
this is very code intensive.
I'll just alert you,
there are gonna be pieces
where I'll show you, and
your attitude will be,
come on, show me something I don't know,
but my experience is
if I leap ahead on this
and all of a sudden dive in the middle,
you'll, how did you get here?
So just let me just sail through it,
but don't hesitate to stop
and ask me a question,
a clarification if there's
anything that you'd like me.
I prefer to be interrupted
while I'm talking
rather than do all this stuff at the end.
Anyway, what's friendship about in C++?
It's a keyword, you can put it in a class,
associate it with a
function, or another class,
and what its primary role
is, is to grant access
to the class granting the friendship
to something that normally
wouldn't have that access.
And you use it judiciously
because you don't wanna,
access control is a big deal in C++,
you don't wanna give
it away unnecessarily,
but there are clearly times and places
that you need to do this.
But what I'm going to do is show you
a use for the keyword friend
that's actually not about granting access.
It's about making something else click.
And in this example, I hope
you'll be able to see that
what it does is it actually
simplifies the implementation
of certain aspects of a class
and it also produces a better
interface as a benefit.
What I'm going to use as my example
is a class for rational numbers.
I think it's a nice,
easy-to-understand example
because most of us know how
to do fractional arithmetic,
we know what the purpose of
something like this would be,
and what example use
cases would look like.
Now, in order to keep
the examples readable,
I've left out certain details,
like there is an ample
opportunity to use constexpr
in the code you're about to see.
I'm not doing that just
because it adds verbiage
and clutter to the slides,
but just remember there's uses for it.
Also, you can say the
same thing about inlining.
I will throw the keyword
inline in a few places,
but not in every opportunity,
just again, to minimize
clutter in the code.
Here is a first cut at a
class for rational numbers.
In this particular case,
my starting version,
it's not a template, it's just a class
that implements the
numerator and denominator
of the rational numbers
as sidelong integers.
And, as you might expect,
it has a small assortment
of constructors that let you do things
like make me a rational with
a reasonable default value.
Make me a rational number
whose initial value is
the same as some integer.
So, for example, not
surprisingly, if you say
make me a rational whose value is three,
you get three over one,
that sort of thing.
And it has the default implementations
for the copy constructor
and copy assignment
because this is a data type
that has a shallow implementation,
a compiler-generated copy,
operations work just fine,
and it has no move semantics,
so we don't have to worry
about our value references,
and the move operations.
It also is going to have,
there's just one teaser
there, a plus-equals operator.
It's one of many
arithmetic operations
that you would provide
for a class like this.
But I've drawn your attention
to the converting constructor.
And what makes it a
converting constructor is...
There's a little disclaimer here.
This is the old definition.
It's good enough for our purposes.
In C++ 03,
you would simply say a
converting constructor
is any constructor that
can be invoked with a single argument.
It could actually be a constructor
with more than one parameter
if the parameters have default values.
The key thing is you can
call it passing it one thing.
And it's not declared
with the keyword explicit,
because the keyword explicit
is there to turn off
the property of a converting constructor.
And in an arithmetic
data type like rational,
turns out the converting
constructor is really powerful tool
for providing an interface
which makes this
arithmetic type behave like
the built-in arithmetic types.
In particular, you can do
things like that, r1 += 42,
where you're adding an
integer to a rational number.
I have to make sure I don't
use the word rational.
It seems like a rational thing to do.
It seems like a fairly
intuitive thing to do.
The understanding is though
that when you write that r1 += 42,
the compiler steps in
and says, "I don't see
"a function whose signature
matches exactly that.
"Ah, but if I apply the
converting constructor there,
"I will get an argument match."
So it will on the fly manufacture
a temporary object of type
rational initialized with the value 42.
As-if.
What's shown on the bottom
of the slide is really
what gets done;
it's that we're creating
a temporary object
and then using it and
then throwing it away.
That's the as-if model.
The compilers are pretty good
at optimizing this stuff.
Conceptually, that's what's happening.
Now, in addition to the
assignment operators
like plus-equals, minus-equals,
the class ought to have
binary operations like just
plus, minus, times, and divide,
where you could say r1 plus r2.
Something like that.
Now, here's where we run into
an interface problem which is
not too hard to solve.
If we want this thing to really
look like a built-in type,
we should be able to say,
not only r2 * 2, but 2 * r2.
It should be symmetric,
in that you don't have to
have both a rational as the
left and right argument.
You can use mixed arithmetic
just like you can with integers
and floating values.
And based on what I showed
you, this doesn't work;
and the reason is that the second one
has a non-rational as the
left operand of the multiply.
On the previous slide,
notice that these operators
are all implemented as members
which have an implied parameter this,
which is the left operand of
those arithmetic operations.
The compiler will not employ
those member functions.
When the arguments are
an int and a rational,
it won't do a conversion
on the left operand.
And so this is the conventional reason
for implementing these things
as non-member functions
rather than as members.
It's so that the compiler,
during overload resolution,
will try to do a conversion either on
the left operand or on the right operand.
If those operations are
implemented as members,
it will only try the conversion
on the right operand.
That's sort of one of
the canonical examples
for why we would use non-member operators.
Now the implementation is fairly
simple and straightforward.
It's just return
a rational constructed by
multiplying the numerators
and multiplying the denominators.
Implementation is really pretty trivial.
But the little twist here is,
this is now a non-member function,
and it's implemented
by referring to private data in the class,
and that's an access problem.
So, what do you do?
One way to do it is to grant friendship.
This is what a lot of people think of
as a legitimate use for friendship.
And it arguably is.
And now you can go ahead and implement
all of the operators,
multiply, divide, add, subtract
as friend functions like this,
and the bodies are all
just the straightforward
operations that I just showed you.
But there's arguably a
better way to do this,
which is that one of the
properties that you would expect
based on your experience
with the built-in types.
There is a relationship
between the multiply
and the multiply-equals,
between add and add-equals.
You would hope that they would produce
the same arithmetic result.
And during a code review,
that's one of the things
that you might want to assure
yourself of, that, hey,
if I use *= or *, I'm
gonna get the same result,
it's just delivered to me
in a slightly different way,
but it's the same numeric result.
One of the ways to assure that is,
why don't you just implement
one in terms of the other?
And it turns out to be very
natural to do it this way,
to simply implement the
non-member operator *
by calling the *= to do the math.
The hitch in this particular
packaging though is,
notice that the operands of the operator *
are both passed by reference to const.
We do that,
this is an example of using
pass by reference to const
to mimic the behavior of pass by value.
And in this case,
the fact that the left operand
is by reference to const means
that you can't use either
the left or the right operand
to accumulate the result.
If you say, x times y,
you don't expect to change x,
and you don't expect to change y.
You expect the answer
to go somewhere else.
So you have to have a local object
to hold that result.
And then you use the *= operator there.
But, in fact, there's a clever way
to avoid having to
declare that local object,
and that is to do it this way.
Like that.
It just cuts out a lot of code.
I use that weird spacing
there just so you can see
the difference; and the difference is
I'm making the left operand pass by value,
so it, in effect, is the local object
that accumulates the result.
And because it's not declared const,
it's just passed by value,
you still have the assurance
that the actual call
doesn't modify the left
or the right operand.
I didn't really examine this
carefully, but I suspect
that modern compilers
will do a pretty good job
of optimizing both of
these into the same thing.
If one is gonna win,
this one might be
more conducive to optimization.
And this avoids granting friendship.
So now, in this particular case,
we have an obvious relationship
between the multiply and the
multiply-equals operator,
that it's very natural to
want to implement multiply
in terms of multiply-equals.
And that's true for add, subtract, divide.
It's not true for every
one of the operators.
For example, there isn't
this relationship between
greater than and greater than or equal to.
It's not the same natural fit,
that one is obviously a member
and the other one is not.
In the case of something
like a greater-than operator,
it's not obvious that there's some member
that you can call on.
You might just be inclined to just
make the greater than a non-member
and make it a friend,
and it accesses the numerator
and denominator directly.
So in this case, granting
friendship might be simpler.
Here's the other sort of canonical example
for why you use non-member functions
instead of member functions, and that is
if I want to implement an output operator.
Use the shift operator
in the conventional way
that it's used with the stream operations.
In order to be able to
output a rational number,
again, you need access to the
numerator and denominator.
You need to be able to display those.
And so it seems at first blush
that what we want to do is declare the
output operator as a member
of the rational class.
Clearly,
you will not be very popular
if you go and start editing IO stream
to change the stream class
to include a rational
output operator; that's not
the way you go about it.
You go about doing it like
augmenting the rational class
to provide the output operation.
So you make it a member, the problem is,
if it's a member of the class,
the rational number is
gonna be on the left,
and the stream will be on the right,
because once again, that's
where the expected placement of the
class object is and a member
function is, it's on the left.
And this doesn't mesh
well with all of the other
stream operations where
the stream is on the left.
So how do you fix that?
Well, that's another use
for a non-member function.
In this case, the non-member
function is not being used
because we want to permit conversions
on either the left or the right operand.
We just want to control the order
in which we declared the
arguments to the operator.
We want the stream to be on the left.
Again, if we make the output operator a
member of the rational number class,
we're gonna wind up with
the stream on the right,
and we don't have any choice.
This gives us a choice.
So you make it a non-member,
but it still needs access to
the numerator and the denominator
of the rational number.
So it looks like it needs friendship.
There's the implementation;
it needs to be a friend.
A common idiom is
to go ahead and declare a
member function called put,
where the invocation looks like
the last line just before the
curly brace, where you say
the output stream.put rational number.
It doesn't look very pretty,
but it does the job well.
And then you implement
the output operator,
as I describe it, a paper-thin wrapper,
around a call on
that not-so-attractive output
operator, the put function.
Again, you always have to stop and think,
is it worth my while
to create, in a sense,
this member function that I'm
not gonna ask anybody to use
and create it as a public member function
just so that I don't have
to grant friendship here?
That's a call that you have to make.
But one way that you can tip the balance
in favor of that function
is that sometimes
you wind up implementing that put function
as a member of a class and hierarchy.
You have a base and a derived class,
and you put the put
function in the base class
and make it a virtual function
so that you can override
it in the derived classes.
It then turns out that using this idiom
actually gives the output
operator polymorphic behavior
if it's implementing in terms
of a polymorphic put function.
(students speaking)
Yes, that is a typo.
Thank you for catching that.
Let's just fix that
just so that
everybody sees clearly what it is.
It's very nice to know.
In fact, that's a reason to plant typos,
is it's the way you can find
out if people are actually
listening to you and paying
attention to what you're saying.
- You planted it?
- Yes, that's right.
So I did that on purpose, thank you.
Thank you very much.
Okay.
Alright, so now I'm gonna
show you a slightly more
elaborate example just to
illustrate some of the
issues that are involved.
So here is a rudimentary function
that computes the mean for a
collection of rational numbers
in an array.
Basically, I'm just summing up
all the elements in the array
using the plus-equals
operator from the class,
and when I get done, I divide that sum
by the number of elements
in the collection,
and that should give
me the arithmetic mean.
Were I to replace all
those uses of rational
with some built-in type, like
an integer type or a floating type,
the code would be the same.
And that's the triumph of
operator overloading, is
that's what it's supposed
to be able to do, is
give you those capabilities.
And once again, I want
to draw your attention
to the fact that that return statement,
sum divided by, /=n,
that's mixed-type arithmetic.
You've got a rational on the left
and an integer on the right.
The compiler silently invokes
the converting constructor
to make that work.
Converting constructors are
wonderful for this purpose.
- [Student] Whereas if you had explicit,
it would not do that?
- That's right, if you declared
the converting constructor explicit,
then you would have to explicitly
write what is on that
last line in the slide.
See, because there are ample examples
of classes which have a constructor
that can be invoked with one argument
where you don't want
that implicit conversion.
In fact, like it's not uncommon
to find a container class
where you can do something
like, say, make me a container
initialized with n,
where n is the number of
default constructed
elements in the container.
You don't want that to
be an implicit conversion
from n into a container type.
Things like that.
But in this particular case it
works just well, really well.
Here's another example,
which is that, in the
return statement there,
this is computing the median
of a presumably ordered
collection of rational numbers,
assuming the rationals are already
in ascending or descending order
if I want to compute the median value.
And the number of elements
in the collection is even.
The way you do that is you
compute the arithmetic mean
of the two middle elements.
So that's what that's doing; it's adding
the two middle elements
and then dividing by two.
Once again, it's mixed-type arithmetic.
And so the compiler is going to implicitly
convert that two, that
integer, into a rational
so that it will use the operator divide
that's the non-member function
associated with the class.
And this is all very
much expected behavior.
So, once again, here it is
in more elaborate detail,
that if you write that,
the compiler is actually
manufacturing a temporary object
initialized with the sum of
those two middle elements,
and then it's returning
the result of that
temporary divided by two.
Now, here's where it gets interesting,
is that rational numbers
are a candidate,
a good candidate for turning
into a template because
it's easy to imagine that
people are gonna say,
I like what you've done there,
but I don't want to be obligated
to do all my arithmetic
as long precision.
Can't I have long long,
or something even bigger?
Or if I'm pressed for space,
maybe I want to represent
it with less precision.
So we'll turn it into a...
And this is when all the conversion rules
get really interesting.
So turning the rational
class into a template
is not exceedingly...
There's a little bit of tedium involved
in throwing template typename
in all the right places,
and tracking down the use of
long and replacing it with T,
like that.
It's not too much, but
just got to be careful.
And so here is a sketch of the
rational class as a template.
And once again, it's got an
assortment of constructors.
It has the defaulted copy operations,
and then it has the assortment
of plus-equals, minus,
the assignment-equals,
the op-equals functions.
And then, in order to implement
the non-member binary operations,
we're gonna need
a whole set of additional
function templates.
See, each one of these function templates
is implementing plus
and divide, et cetera.
This is outside the rational
class itself because we want
these operator pluses,
operator minus, et cetera,
to be non-member functions,
once again, because we want the
conversions to apply on both
either the left or the right operand.
But they don't have to be friends,
but they're good candidates
for inlining as well,
maybe even constexpring.
And so,
having transformed our rational
class into a class template,
hey, it looks like
things are humming along.
It's behaving in the way
we would like it to behave.
I can write r1 = r2 + r3
and it compiles and
produces credible results.
Now let's go back and look at our
functions that compute mean.
Do they still work?
Well, it turns out
that this one,
it still works great.
What we've done is we've turned mean
into itself a function template
where the type of the
elements in the collection
is now a parameterized type so that
we could compute the mean
of a collection of ints
or the mean of a collection of floats
or the mean of a collection
of rational numbers
for some precision.
It works.
So it looks like everything is great.
Until we get to here.
This one, it turns out,
is a bump in the road.
And the bump occurs, once
again, on that return expression
which has mixed-type arithmetic.
What we're doing is we're adding to,
in this case, presumably...
By the way, if we
instantiate this function
where type T is a built-in type,
this just works great,
and all those operators
in there turn out to be
invocations of the built-in operators.
But if we instantiate
this type, this function,
we invoke it for a type like a
collection of rational longs,
turns out the last line
doesn't compile now,
that the compiler is unable
to recognize what's going on.
And here's the details of
what's going on, is that
in the case of this expression,
we have two objects of the same type,
essentially, a[m - 1] and
a[m], add it together.
That is invoking one of
the function templates,
the operator plus function template,
which takes two rational
objects of type T.
This is an exact match and
the compiler says cool.
I can figure out that you're invoking that
function template operator plus here.
But then it sees the divide,
and that's a rational of some
type T divided by an int.
Whereas when we were not using a template,
the compiler is able to do that,
now that the type of those elements
is the specialization
of some rational type,
compiler gets lost.
It cannot recognize which divide operator
we're referring to.
And you can test that out by saying,
well, maybe if I force it in the direction
that I want it to go, I say not to,
but to explicitly convert it to type T,
now the compiler says, "Oh,
okay, I can see how to do that."
and it does it and produces
the expected result.
And it has to do with the
difference between the
overload resolution and
template argument deduction,
is that the extent to which the compiler
is willing to apply conversions
to operands in order to find a match,
when it comes to overload resolution,
where you're not dealing
with templatized functions,
the compiler does not require
that the arguments to
an overloaded function
be an exact match.
It will try a conversion sequence
to the operands in order to say,
if it's not an exact match,
can I convert this thing
into something that will match?
And this is well
specified in the standard.
There are things known as these built-in
conversion sequences and
user-defined conversion sequences.
Without getting too much
into the details, basically,
the compiler will try a sequence
of up to three conversions
in order to get from the argument type
into the parameter type to make a match.
But when the compiler is
saying, "Oh, I recognize that
"you're trying to apply a template here."
all of a sudden it becomes
really stingy about applying
conversions to the arguments
in order to make a match.
And that's what happened here,
was that it wasn't willing to apply,
automatically imply that
converting constructor
in order to figure out,
ah, if I convert that,
now I can do a template specialization
of the appropriate operator.
That's an unfortunate consequence.
Having the template is really nice
because it makes the rational class
much more flexible.
In fact, there's ample practice of this.
For example, the standard
libraries complex data type
is a template which you can specialize
for different floating types.
Well, for rational it seems like
that's what you want to do too.
But the downside is you lose
some implicit conversions
which seem like they're
the natural thing to do.
You lose a little usability.
So one way to do it is
just take your lumps,
say, okay,
it's a good trade-off
as far as I'm concerned.
I like the flexibility of
the rational number class
as a template.
I'm willing to tolerate that I have to
do an explicit conversion now and then.
But here's an alternative,
is that you could go ahead and say,
why don't I, in addition to implementing,
a divide operator,
that takes two rationals
specialized for type T,
overload it with a couple other functions
that will be more flexible
in their matching.
Notice the differences.
This is a template with a
single type parameter T,
which is the precision type
of both the left and the
right rational number.
These two templates have
two type parameters,
capital L and capital R,
and what they're doing is
they are saying,
I'm gonna give you a
way to divide a rational
by something that's not a rational,
or to divide something that's
not a rational by a rational.
Now the presumption is that
whatever the type of R is,
it's a type that's convertible
to the type of rational,
of L, for example,
and that if their
conversion doesn't exist,
you'll get a failure inside
the template instantiation.
But on the presumption that
that conversion exists,
it turns out this works.
This will allow you to go ahead and write
this original expression back
there, the one that's marked
at the top there.
You can do it, but it
causes an explosion of code.
You have to do, essentially,
a trio of function templates
for every one of your binary operators.
It's tedious and error-prone,
not a really preferred
solution to this problem.
It's not clear it's worth it.
So anyway...
By the way, I did all this
work like 15, 18 years ago.
A lot of this was inspired by,
I do training for a living,
and I use this rational number class
in some of my training materials
to illustrate a lot of the principles
which I was just talking about,
about how these conversions work
and how converting constructors
play a role in all this.
In my line of work, because I
want to cater to my clients,
and my different clients
will use different
and say, "Can we use your
examples with this compiler."
and I want the answer to be yes.
I don't want to come in
and say, you must use...
It only works for the
new compiler Toolchain.
I haven't tested it with anything else.
That usually doesn't go
over well commercially.
So I was pretty conscientious
about testing this stuff.
And what I found at the time
was I was actually bopping back and forth
between five different compilers,
and two of them were perfectly happy
with the code that I just showed you,
it compiled, linked, and executed.
But then,
I was doing some work and found that
two of them would accept the compile,
but they failed the link.
And then there was one other
that I'll get to shortly
that was giving me interesting feedback
which actually prompted
the rest of this lecture.
So here was the version that I was using,
in particular, that
triggered this analysis was,
so I have the rational constructors.
As before, I have the member,
plus-equals, minus-equals,
and I had implemented all of the
non-member operators: plus,
minus, times, and divide
as separate templates, and
I hadn't implemented the trio;
I just implemented one function
which takes two rationals.
I was living with the fact
that we had to do the
explicit conversion sometimes.
But, in this version I also
had an operator greater than
which allowed me to compare one rational
greater than another rational.
And I also had the output operator,
and those two functions
were not implemented as non-member...
They were implemented
as non-member friends
that had direct access
to the data members.
They weren't implemented the way plus was
through the intermediary of plus-equals.
And I did that, and the
compilers were all over the map
as to whether or not they
were willing to accept this.
Some did, some gave link errors.
It turned out that one
gave me a compile error,
which I'll show you in just a moment.
So anyway, what really piqued
my interest at first was
that the ones that were telling me
that there were link errors.
There it is, there's
the friend declaration
for operator greater than.
And right there in the same header file,
right below it was the
template operator greater than
that takes two rationals of type T.
That one takes two rationals,
and this one,
this operator greater
than takes two rationals.
What's your problem,
linker? It's right there.
Same thing with the output operator,
getting the same link errors.
Now, just by way of background,
before I do any further analysis,
I just want to point out that
there's a little syntactic
quirk in C++ which is that
when you're inside the body
of a class template like rational T,
you can mention either
rational or rational,
and either of those will work.
In other words, this
is the same as this.
And if you wish, you can
toss a coin on every usage
and randomly leave out the
Ts or add them at your wish
and upset your colleagues
in a code review.
But these are equivalent.
So with that little bit of background,
here's what I thought was happening
before I knew better,
is that when I wrote
the friend declaration
for the output operator,
that since this friend
declaration appeared
inside the scope of the
rational class template,
that occurrence of rational right there
was as if I had written rational T.
In other words, that if I
instantiate rational int,
I was granting friendship
to an output operator
that also took a rational int.
That's what I thought would happen.
And similarly, since...
The other issue was that when I declared
the output operator
as a non-member function template,
since this was not inside the
scope of the class rational T,
the T there in the parameter
list was not an optional T.
And it turns out I had a misunderstanding
of what was going on, and let me show you
what is really happening.
Here's the hint that I got,
is that the GNU compiler
was the one that would
not compile my code;
it rejected it with this error message.
And it said,
friend declaration declares
a non-template function.
By the way, this is literally
what the error said.
I'm not making it up.
And then that long parenthetical remark.
And I think it's kind
of hard to read, so...
Actually, what I should say is,
it didn't give me an
error; that was a warning.
Yeah, it was a warning.
So I've broken it up onto bulleted points
to make it a little easier
to read that same thing.
Let's read it carefully, it says,
it was complaining
on both the friend declaration
for the greater than
and the friend declaration
for the output operator
inside the scope of the class.
And it said,
the friend declaration declares
a non-template function.
It was saying that both...
Conceptually, what I was expecting was,
I have this class template rational T,
every time I instantiate rational int,
a rational long, a rational
long long, I should get
a greater than operator
that operates on that type.
I should get an output operator
that operates on that type.
And it was telling me,
but that thing itself,
those greater than operators
and those output operators,
they weren't templates; they
are non-template functions.
And it said, if this is
not what you intended,
then make sure that the
function template has already...
In other words, if I
intended them to be templates
and not non-templates,
then what I should have done was made sure
that they were previously
declared as templates,
and make sure I use the angle brackets
in the function name of
the friend declarations,
which I didn't do.
By the way, I checked the C++ standard,
and I actually found,
hey, GNU wasn't making this up.
It's actually in the language.
This example I've reformatted a little bit
to make it a little easier to read, but
this comes right out of the
C++ standard, it's still there.
So what we have is, at the bottom,
a class template called task.
And notice that it has two
friend declarations in it.
The first of those friend declarations
is for something called process,
and the second one is called preempt.
And what it's saying is that
the second friend declaration
is actually granting friendship
to the specialization of a template.
And it comes to that conclusion
because of the presence
of the angle-bracketed T
after the name preempt.
But if you compare that with
the treatment of process,
process doesn't have
the angle-bracketed T.
And so what happens is each
time you instantiate task
with a different argument type,
it grants friendship to a
function called process,
which happens to take a pointer
to a task of the right type,
but that process function
itself is not a template.
- It's not defined.
- That's right.
In other words, I granted friendship
to something that wasn't defined anywhere.
That's what's happening.
Kind of curious.
What happened was that friend,
right there, that output operator
was not a template, it
was granting friendship to
a function that wasn't defined.
So this is being more explicit about it.
So if you were to instantiate
rational with type int,
then that class template instantiation
would be granting friendship
to an output operator
that takes a rational int.
And again, if you
instantiate it with uint64_t,
it grants friendship to that,
but neither of those output
operators is a template.
- [Student] Can you go
back to the slide where
you showed it was sort of optional?
- Oh, you want to go back.
- [Student] That's also
what made this complicated.
- You're talking about
this? The fact that...
- [Student] Yeah, that
also made it complicated
because you might have randomly
chosen one way or the other.
- It's a trap, it's a syntactic trap
that you could fall into if
that's what you're suggesting.
- I think so.
- Yeah.
By the way, I didn't
mention, but I'll say here,
I prefer to do it that way.
I leave it out.
I think it's less
cluttered, and it's clearer.
- [Student] But isn't?
When you leave it out,
I thought that's a problem too.
- No, it's the special
case of the friends.
Let's work through these friends.
So here's what I think that...
So this is now where we are
from the user's perspective
with what I've given you, which is,
as a user,
I want to be able to use
the rational class template
and instantiate it for
some type like int there.
Just create an object
r of type rational int,
and I want to be able to write it out.
The problem is that
when I instantiate rational
with type int as its type argument,
it will grant friendship to a function
that's an output operator
that takes a rational int,
but it never produced a definition for it.
And so what happens is,
I then become responsible
to implement that output operator
if I want my code to link.
And this is what's known as
an unfriendly user interface.
You would not use this class
if this was your responsibility
to implement those functions.
You expect them to be generated
somehow by the template.
So following the error message
or the warning that I got
from the GNU compiler,
I said, okay, let's try this.
It looks like what you want me to do,
you being the GNU compiler,
is throw those angle-bracketed Ts in there
so that I'm granting friendship to
specializations of those functions.
In other words, I'm assuming
that the greater than operator
and the shift operator are
themselves function templates,
and that somehow or another
they will be instantiated upon use.
Right? Makes sense?
By the way, just as a lexical
issue, the spacing there is,
you don't need it.
So you get these
curious-looking things like
the bow tie operator.
If you turn it sideways, it's a sergeant,
there's three chevrons for a sergeant.
Now, this was adding to the puzzlement.
By the way, I was doing
this quite some years ago.
This stuff has since been fixed.
It was interesting to me
to see how this chain of
quirks among the compilers
ultimately led me to a solution
that actually still works
and holds up well, which is
that the GNU compiler accepted.
I just put in those
angle-bracketed Ts, and it said,
I'm cool with that and
compiled and linked the code.
And I stepped back and
said, hold it a second.
That shouldn't have compiled because
in order for the compiler to recognize,
for example, greater than as a template,
you have to declare
operator greater than as a template first.
In other words, this
is just using the name,
putting angle brackets and
a T after it and saying,
treat it like it's a template.
It was never declared as such.
And in fact, the warning
that came from g++ said,
make sure the function template
has already been declared
and add the angle brackets
after the function name.
And despite the warning,
the GNU compiler compiled it anyway.
This is what I think they
were telling me to do, is
if I want the angle bracket
on the operator greater than,
I have to precede the
whole class definition
with a declaration of
the function template.
Right? I have to announce that
greater than is a template
before I use it as such.
Okay?
But now look at the part in the teal,
or green, whatever you want to call that.
That is referring to
rational angle bracket T
before it was declared as a template.
What do we do?
Well, it turns out you need to precede
the declaration of the
operator greater than by
a declaration, not a definition,
of the class template.
And so when you're all done,
you have to have that
chain of declarations.
And that operator greater
than in the middle
is a placeholder for
every one of the functions
that is a function template
that you're granting friendship to.
Right?
If you want this to work this way,
you got to get all these pieces in place.
Kind of hard to do, it's kind of tedious.
By the way, so at this point,
I went back and I said,
okay, let's check this
with all of my compilers,
and four out of five ain't bad,
but it turned out the fifth
one was Visual Studio.
I was not prepared to say,
oh, you know, just tell my clients
you can't use that tool with my examples.
That's just not gonna go over well.
And Microsoft,
their compiler at the time
looked at that angle-bracketed
T there and they said,
whatever that is, I
don't know what that is,
whatever it is, it's a syntax error.
It just wouldn't accept that at that spot,
and I said, then what do I do?
And I had a flashback.
It took me a little while,
but I remembered that
I was doing this work in the late 90s,
the early part of the millennium, and
compilers were still not in agreement
about a lot of these corner cases.
And I remembered that
early versions of the Microsoft compiler
had this as a chronic problem.
The inability to associate
a friend function
with a definition that
appeared outside the class.
And I almost invariably
solved the problem by saying,
just take the function definition
and put it in the class.
Now the declaration and the definition
are all in the same spot,
and it turned out that the
compiler wasn't confused anymore.
You didn't have to struggle
to match up the declaration
with the definition.
So I refer to this.
This is not a widely accepted term,
but I think it's a useful term, is
there are two ways that you can
have a function have the
attribute of being inline,
a class member function.
One is to explicitly declare
it with the keyword inline.
The other one is to place its
definition inside the class.
And people will say, well,
that's an inline definition,
but how do you distinguish that
from one that uses the keyword inline?
And I used the Latin phrase in situ,
meaning in place or in position.
This is an in situ definition.
Yes?
- [Student] It's a friend
that we're dealing with.
- Yes.
Is that how you like to refer to it,
or you just made that one up?
- A friend with benefits.
- A friend with benefits, okay.
(student speaking)
Run that by me one more time please.
- [Student] I think you can
put a whole template operator
inside your class.
- How is that different
from what I have done here?
- [Student] You put
like a template friend.
- Oh, actually, you use the word template
and you actually make a member template?
- [Student] Yeah, can you?
- I haven't tried that.
So I can't say it off the top of my head.
If it's a member
template, why would you...
Oh, you're talking about putting
a member template definition
physically inside?
I think you can.
I think the syntax permits that.
In this case, I didn't need it
to be a member template.
It turns out that just
making it a friend function
defined inside its class worked.
In other words, the
effect of this is that,
as I said, this compile
link, but what's going on,
let's actually look at this, is that
when you place not just the declaration
but the definition of a friend function
that itself is not a template,
but you place it inside
a template like that,
every time you instantiate
the class rational for a different type,
it not only grants
friendship to a function,
but it manufactures the function
that it's granting friendship to.
That's where this idiom gets its name.
The class template instantiation
is making its own friend.
And that function though
itself is not a template.
It's a non-template function
that is manufactured as a side effect
of the template instantiation.
Which is as good as it being a template.
In fact, as you'll see in just a moment,
it's actually better than
if it were a template.
- [Student] You call this a corner case,
but the stream operator is quite useful.
- It's that
this is something that you do often,
but it doesn't leverage
itself to a lot of other
different use cases.
- [Student] But if it makes you
throw your hands up and
say, I give up on templates,
and you try to figure out that warning.
- Yeah, that's right.
- [Student] Especially linking problems.
You start including CPP in the bottom,
you don't even know why we're doing...
- If you're taking issue
with my having called this a corner case,
I'll easily relent on that.
The judgment of how widely you use this.
- [Student] Not confirmed by this.
That's why I'm like, oh,
yeah, this is a real thing.
- This is a real thing.
(student speaking)
Take the definition of this operator.
- [Student] Just leave the declaration
and the definition move out.
Will it still work?
- You want to just put a declaration
for the operator greater than
here but not the definition?
The problem is if the
definition isn't here,
it grants friendship to a function
which is not automatically manufactured.
The responsibility of
manufacturing that definition
falls on the user of the rational class,
not the author of the class.
And that's where the
usability issue comes in.
But by putting the
definition in the class,
you not only get the
granting of friendship,
but you get the
manufacturing of the function
that you want to grant friendship to.
- [Student] And if you want
to provide the definition,
but do you need to provide this operator
outside of the class?
- Well, actually, hold that thought.
I have some examples that I
think will address your concern.
- [Student] I do this sometimes.
- I think people do this,
but they don't realize
what's happening.
It's the backstory
that's interesting here.
(student speaking)
It doesn't compile, that's right.
By the way, there's a book
by Barton and Nackman,
Engineering and Scientific
Programming in C++.
It was a popular book
back in the late 90s.
After I did all this work,
and I gave a talk on this many years ago,
and I own a copy of Barton and Nackman,
and I didn't notice that they were using.
I hadn't read the whole book.
And if you look through the book,
they never mention, there's
nothing in the index
that points out this technique.
They just sort of,
in one of their examples,
they just threw it in
without comment as if,
what else do you do?
Without appreciating its significance.
So it's not like I
invented this originally.
I think the first written
usage of this technique
was in that Barton and Nackman book.
It was that I don't think they appreciated
that, hey, this is actually a find.
They didn't really
explain what was going on.
It was a throwaway line in the book.
So now, by the way, let's
revisit this one, which is
this problem we had with
the compiler's inability
to recognize what was going on
with this mixed-type arithmetic.
As a concession, we wound
up having to explicitly cast
that integer operand 2
into an object of type T.
And the reason why I tolerated it was that
the cost of fixing it was
turning a whole bunch of single templates
into a trio of templates and doing that
umpteen times for all the
different math operators.
I said life is too short.
And so as a reminder here,
the fundamental difference
between what happened,
why it worked when we weren't
dealing with templates was
when you're dealing with
non-template functions,
when rational was not a template,
it was using overload resolution,
and overload resolution
uses a fairly generous set
of user-defined conversions
in an effort to make a match,
and it includes the use of
converting constructors.
But when we switched turning rational
and all of its operators into templates,
a lot of those conversions
were now not available
during template argument deduction
to figure out if we could make a match.
And so one of the nice properties
of this making new friends idiom is
that if you turn all of the
operators like divide and
multiply and add and subtract,
instead of making them
non-member function templates,
you make them friends,
you actually move the definition
of all of those binary
operators into the class
and make them friend functions
that are defined there.
Now you're no longer using
template argument deduction,
you're back to using overload resolution,
and the compiler will
use a richer assortment
of implicit conversions in
order to make the match.
In other words, we
simply take the rational
operator divide which was a
non-member function template,
you move it in there.
It's no longer a template.
It's an overloaded function.
- But it's in a template.
- It's in a template, yeah.
But now what happens is,
you do this even for a function like that
which doesn't need to be a friend,
you put it in there.
And then what happens is,
now that divide by two
compiles without the explicit conversion,
which is very nice.
- And you didn't have to
write all those other.
- That's right, I didn't have
to write the trio of things.
That raises some questions.
Oh, by the way,
notice that the implementation
of the divide operator
is in terms of divide-equals.
Divide-equals is already
a public member function
that does the work.
There's nothing in the body
of that operator divide
that mentions num and dan directly.
It doesn't need to be a friend.
So why do we have to throw
the keyword friend there?
Because if you take the word friend off,
now the declaration
doesn't compile because
when you put an operator
inside a class like that,
the compiler assumes
it's a member function.
It has a this pointer,
and that divide operator
becomes a three-argument divide,
and there ain't no such thing.
You'll say, okay, then
get rid of one of those,
and so you have it as a
member and with one argument,
but now we're back to the very beginning
where we can't have conversions
on the left argument.
You need it to be a non-member
so you can have conversions on
either the left or the right.
And it can't be a member.
It has to be a non-member.
So the question is, is
the friendship necessary?
And the answer is, in a sense, no.
But if you don't make it a
friend, then it becomes a member.
Oh, but maybe there's a possibility.
Why can't we make it the static?
Because that's the other way
to get rid of the this pointer.
Make it a static member function.
Why can't you do that?
Well, because you can't.
There's simply a flat-out statement
in this standard that says,
operators cannot be
static members of a class,
and because it drives
overload resolution crazy
with creating ambiguities.
It's just a flat-out
rule, you can't do it.
And so, in a sense,
what we're doing here is
we're using friend in a
somewhat sneaky fashion.
It's a way of turning what would
have been a member function
into a non-member helper function
by getting rid of the this pointer.
And so you could argue that, conceptually,
static might be the
better way to declare it,
but you can't do it that
way, so we're essentially
leveraging the behavior of friends.
So this is granting friendship
when you really don't
need to grant friendship.
It's to deny membership.
That's what's really happening.
Now, just one little last
thing that I want to point out
which I think is interesting is,
so the compiler, when you instantiate
rational for a type T,
it's manufacturing this friend function,
where's it putting it?
In what scope does it reside?
And friends have a very
interesting behavior, which is...
By the way, to drive the point home,
I've now wrapped the rational
class inside this namespace
called Saks.
And if you try to refer
to the operator divide
outside the class like that,
this is a use, I wanted a single-line use,
I'm trying to take the
address of that function
if I can point to it, and it turns out
that it's in the namespace Saks,
I just say Saks::operator/.
The compiler can't find it.
And the reason is, it's
using ordinary name lookup.
And friends that are introduced
in a class scope like that
are not visible by normal lookup.
But it turns out it can be found
by something called the
argument-dependent lookup, which
it relies on looking at
the argument types to say,
what class should I go look in,
or what namespace should
I go look in to find it?
But here's an interesting
little demo that you can do
to demonstrate what's happening, is that
if you go ahead and explicitly declare
a rational int operator divide like that
so that you actually make the
declaration visible by lookup,
that access on the bottom line is found.
In other words,
you actually introduced a
declaration into a scope,
and it does exist.
And moreover, if you actually
turn that into a definition
and you use the rational int type,
you get a multiply defined symbol,
so you can actually find
the spot where the compiler
put those friends into a particular scope.
So they are being manufactured.
You can hunt them down,
but you normally don't;
you just let argument-dependent
lookup find them.
- [Student] Would you have said Saks scope
fractional scope operator?
- No, because it's not a
member of class rational.
You can't say Saks::rational::operator
because it's not a member
of the rational class.
Yeah, even though it's
there, it's not there.
So anyway, Scott Meyers actually
has a little bit of advice
in his book.
He says the rationale for
doing this is, it says
to find non-member
functions inside templates
when type conversions are desired.
I think that's a little
bit too vague for my taste.
I prefer a wordier but
more precise statement,
which is when a class
template defines a type
with an associated binary operator
that allows user to find
conversions on its left operand,
then you should define, not
just declare that operator
inside the class template
as a non-member friend.
That's really the specific use case.
Okay, and that's it.
There are the references,
and thank you very much.
(applause)
