- [Nicholai Josuttis] So hello everybody.
Good morning, good afternoon,
I'm out of any time zone now.
My name is Nicholai Josuttis.
You might know these two
books, maybe the left one.
I'm one of the authors of this.
And I'm now an author
of the second edition.
And I wanted to talk with you about it.
This talk will be about templates,
but at the same time, it will be heavily
about C++ '17 because in the new edition,
all C++ '17 features of
templates are integrated.
So, let's start.
We have several slides.
Let me first tell you a little bit
about the history of this book.
It all started in 2000
when expert, ex is missing there
and the victim, that's me, came together
were brought together by Addison-Wesley,
because we both planned to
write a book on templates.
David Vandevoorde as an expert,
and me, as somebody who was
really troubled to understand
the whole issue and needed good books
to learn it and use it.
As a result, in 2002, end of 2002,
we published this book,
and it's pretty hot, and of
course, after a couple of years,
we thought about doing something more.
It started in 2007,
that we were wondering,
shouldn't we write a new book?
And I looked at one of the
first emails we exchanged
with each other and the publisher,
and David wrote an
interesting sentence there.
He said, regarding a second
edition, it's definitely
something I'd like to see.
One schedule constraint
is that the new standard
is still in flux,
a new feature called
concepts is the culprit.
(laughter)
We still don't have it, but
we have a second edition now.
But what happened then,
two years we waited,
then we started, and then we got fed up,
and tired and out of resources.
So what happens if
you're out of resources?
You add new manpower.
So that's Doug Gregor, who
contributed a lot from Apple
and via Boost and so we
started to write a lot
and we then we all got stuck,
and nothing happened until
I would say, last year, I got
out of a four-year contract.
So I had time to bring the stuff forward,
and I decided it's time to do so.
And here we have it, with C++ '17.
That was actually the schedule
or the plan to have it out here.
So what's inside?
We have 300 more pages.
18 more pages on Type Traits.
We have a major responsibility
for three different parts.
I'm responsible for readability
for the ordinary programmer
so I wrote the tutorial, mainly.
David is the expert
implementing that old stuff
as a reference.
Doug is also the expert,
but it turned out,
he's out already from C++ I've written.
He was surprised about some new
features we have in C++ '17.
But he contributed a lot of,
especially in the application chapter.
You will see examples, from everything
and you will see why this is awesome
or why this is frightening.
It will be both.
So let's start.
What changed over time
and how has programming
with templates evolved over time?
Let's start with Type Traits.
Type Traits was the first application,
measure application of SFINAE,
so the idea, that if a
template is not valid,
is not valid code, it's ignored.
And we use that to say, well, whatever we
check with 'Is so-and-so'
we have two member
templates, test and test
and the one has lower priority,
the lower one, according
to overload resolution,
because the arguments are ellipses.
And the upper has higher priority,
but the moment it gets
invalid, it's ignored.
So that was a whole trick,
and the goal we had was
find something that exactly
fits what we are looking for,
what we want to check in the trait.
It turned out, we started,
when we started about that
we used enums for the resulting values.
In the first edition we used yes and no,
but now in the second edition,
we switched to something new of course,
following the conventions of C++ '11,
so that we, oh no, sorry, first of all
I have to say something different.
One problem here is
that character and long,
we checked for the return type,
the size of the return type
and character and long on some
machines have the same size.
And that's true, they have.
On that machines, even a
long, size of long is one.
Because by definition,
size of long is always
size of character, and
character, if character and long
have the same size,
long has a size of one.
So we needed a better way to
distinguish these two cases,
so we came out, that was
already in the first edition,
with having one character
and two characters
they couldn't, by definition,
not have the same size.
There were various ways to do that.
Okay, so the whole trick
is to use this framework
to define in a trait, to
formulate what you want to check
so that it exactly fails
when it's not the case.
So here it is.
We check for 'Is Default Constructible'
and it's not easy to find
exactly the right thing,
so you might call the default constructor,
it turned out that calling you
with a default constructor is even better.
And because this creates
something, this creates a sign,
we needed a helper taking as a template,
taking the size as an argument.
So these two lines in the middle
are the outcome and then it works, fine.
Next step we did, was well,
enums are not very type safe,
so using yes and no as
an enum is a problem
because in fact we are
returning bools here.
So let's use static constexpr for bool,
and we did that of course.
Okay, the next thing we did was,
in C++ '11 we introduced some better words
instead of one and two, we
have true type and false type.
So we can use them, they
are different types.
We no longer have to check for
the size of the return value.
We just have different types.
It's predefined by the
library, so let's use it.
Our resulting value depends off the type
of which test is used,
and as a test member
functions, or member function
templates returns different types,
this is the way we check it.
The next thing, the next evolution was
that we, that it was
always a trouble in SFINAE
to some extent, to have the
check in a function signature,
because SFINAE, well we
needed it in the declaration,
so either in the arguments,
or in the template arguments
or in the return value or wherever.
So in the meantime, the technique evolved
and it was possible
that we can instead use
an optional additional template argument
so we could add a type name here
just for the purpose of
doing the right check
and disabling the feature
if we don't want it.
So here we check for whether the type,
for the question whether the declar type
of using the default
constructor is good enough,
and surprisingly, suddenly
the new was no longer
possible here, and we could
still check five things,
according to language rules.
So let's use this.
The usage is similar
to the standard traits
you probably all know.
So instead, we use our conventions,
so we have Is at the end of capital "T"
and we print true and false at the end.
You can partial specialize,
full specialize your function,
your class declarations
on that and that's fine.
A couple of things are not possible.
First of all, there's no
partial specialization
for function templates.
And we still don't have it with C++ '17.
So that means, we need
another way to dispatch
between different
implementations of a function.
Functions, to have different
implementations of a function,
we need, unless we have if constrexpr,
we need different type,
different argument types.
But that, so that means we need the types.
But there was another interesting problem.
And the other interesting problem is,
if you print out the value,
so your constrexpr static bool,
to see std: cout, everything is fine.
But the moment you use references,
you can get into trouble,
which surprisingly rarely
happens in practice.
Not many people know that,
and so if you, for example
declare a template, taking a reference
and you pass the value of this type trait,
it might not compile.
For example, not worth clang.
And if you pass it as a bool reference,
it might not even
compile with gcc provided
you use minus 02
With minus 03, it compiles.
And if you use it as a bool sta,
it only compiles with Visual Studio.
So what's the problem here?
The problem is,
that if you declare a static constexpr if
this is a declaration,
this is not a definition
by language rules, before C++ '17.
That means, whenever you
use it and you need ODR,
so the one definition
rule, which is not the case
if you pass it by value.
But in all other cases,
it might, or not all,
I'm not a code expert.
So in several other cases
and the interesting thing
is when you need the address
of the object, for example
so you pass a reference or a pointer.
You, things might fail,
as I've just told you.
According to the
standard, this is correct.
Without definition, you
might get a linker error,
if this is not optimized away.
So you need, in exactly
one translation unit,
a corresponding definition of this value
in your type trait.
I hope you all do that, or
maybe not because obviously,
most things work without.
But the things are better with
C++ '17, because in C++ '17,
we have introduced inline
members, and that means
implicitly declaring a
constexpr static member
is an inline member.
So since C++ '17, the thing
above is a definition.
That's fine, now, portable
even if you pass a reference,
even if you pass a pointer.
And is this backward compatible?
Yes, because if you have
a definition outside,
this is now considered
a redundant declaration,
which is deprecated, so
everything still compiles.
One reason a couple of people
don't run into this problem is
you can solve both problems.
One problem was that for
functions, I need different types
returned by the trait, and this problem,
you can do a different approach.
And the different approach is
instead of defining a value
in your type trait, static constexpr bool,
you define a type.
Here's it called capitalized TYPE,
in the standard, we
call it lowercase type.
So this makes the last line of the trait
even smaller, because we just
initialize our resulting type
as a declar type of the test
function that was instantiated.
And then, we derive from this type,
because it's either true or false type,
then the true or false do the rest
and your member type is declared, etc.
and all the ways you
can use standard traits
are available now for your trait.
This is currently, I would
say, the state of the art
you're using to define
standard, to define traits.
Other, let's try them out.
We can now, compile even with
this technology before C++ '17
we can compile all these
cases where we pass references
or pointers and we can do,
we can implement different functions
against this trait by tag dispatching
that means, if we have a
function we want to call 'foo',
inside 'foo' we call this
trait, and according to this,
we call a helper function,
which is specialized,
specialized for one or both cases
That's the way we do it.
Okay, are there other ways to do it?
Yes, one way standardized with C++ '17
which is the remarkable
simple type trait, excuse me,
utility called 'void_t.'
From different perspectives,
different proposals came out
with the same idea and the idea was,
'void_t' takes an arbitrary number
of types and....... voids them,
which at first, looks very strange,
but what is the application?
You see it here.
You have your different
implementation just saying well,
I'm by default, not default constructible
and then I have a specialization,
and inside the specialization
as a second argument,
I pass a void_t of whatever I want to pass
and whatever I want to check,
as long as it provides a type.
For example, declar type
of the default constructor,
that's the check we need.
As this is a variadic template,
you can pass multiple
arguments here, to void_t.
And that's turned out to be very helpful
although there are some flaws
with SFINAE friendliness, etc.
So sometimes you can use it, sometimes not
or you need additional effort.
Is that all?
No.
Now these young guys come in from school
and think they can do everything better,
and one of them is Louis Dionne,
who is the author of Boost Hana,
you might probably know him
He has another cool idea.
He says, there's far too much
boilerplate in this code.
Let's make it different.
Let's use a definition called 'is Valid'
and I only want to have one line
to define what my trait is checking for,
and for that, I use a lambda.
So you say here,
always with the same syntax,
I get something and in the
return type declaration,
I make the corresponding check,
here using a little helper
that you can define or that
is defined by Boost.Hana.
And that's it, you have defined
is default constructible.
In principle we could provide,
is valid and value 't'
in the standard library
in the next version
and that would be a one
liner to define your trait.
In this case, you defined a trait object,
not a trait type.
Usually, it's the other way around.
So what you might need,
still need, is then to say
well, and the type of
this trait function object
is my trait type, and everything works.
You curious how it is implemented?
It took me awhile to find it out,
or to at least roughly understand it.
I'm not able to explain
it, but here's the code.
It's a nested application
of generic templates.
So the technology only
works since C++ '14,
and you'll see that you have
one template where you pass
the function, and then another
template where you pass
the arguments, and you more or less,
the function F is your
lambda you want to check
against the arguments
and the rest is the code,
how it works, and the
perfect forwarding etc.
with all these strange language rules.
By the way, one thing I was
very confused about was,
that I.....
in the outer lambda.....
I have "F".......
which I don't have to capture
into the inner lambda,
although I use it,
because, not the object is used,
this is used as an unevaluated context.
So this is used in decl
type, and there this is okay.
You don't have to capture
to evaluate something
in a lambda with delc type.
You all knew that, huh?
Okay, how do we deal with that?
Well, we have a type system.
We have all our types here.
In the standard library,
Howard Hinnant has provided
this really nice picture
of our type categories
we have invented and for each and every
name here, we have corresponding traits,
like is null pointer,
is scaler, is object,
is member pointer.
So you get a rough idea how
our type system is organized.
I found this incredibly
helpful, this picture
to understand at least part of the traits.
For a couple of other
traits, I had some problems.
There were some surprises.
Here's an easy one.
So if you remove const
ints, it doesn't work from a
const int reference,
because a reference is
const, you are not allowed to modify it,
but it's not const from a
conceptual point of view.
Where it refers to, that's const.
So remove const has no effect.
To convert a const int reference,
you have to remove the reference first,
and then the const ints.
If you change the order,
things are wrong, or
don't work as expected.
That's one reason we
probably will have in C++ '20
and hopefully we'll have a trait called
'remove.......cr' or something like that.
Then we have traits where
we have undefined behavior.
So this is also surprising, I think,
because often if there
is no valid behavior,
we got back the.....
type that we did put in.
But this is not the case in all traits.
Some have undefined behavior
and if you're lucky, your
code doesn't compile.
If you're not lucky, it
works on your platform
and you go to another platform
and suddenly it doesn't
compile, or even worse.
Here's an example make_unsigned
does not work for reference.
It has requirements.
This, there was for C++ '17,
a proposal to say......
we should never have
undefined behavior and traits.
Instead we should have
a compile time error.
It turned out that our traits
were so fuzzily defined
regarding undefined behavior,
that this was a step
we didn't want to go, and hopefully,
we will clean it up until C++ '20,
because there's no reason
that this is not a compile time error
if it's undefined behavior.
This is surprising, I think.
Isn't int copy assignable?
Yes.
Can I assign an int to an int?
No.
And the reason is, is_assignable
evaluates a value category
and int is a pr value, so
that the answer here means
I can't assign 42 to 42.
If you know the answer, you understand it.
But there's a reason we
have 40 pages in the book
explaining all the standard
reference type traits.
And this is the last one.
According to the reference
collapsing rules,
we implement here the corresponding rules.
Okay, I skipped this.
Let's do a different topic.
Strings and auto and fold expressions.
What happens if we want to
use a string in a template?
A string literal.
Well, it has constraints.
The first constraint is we are
not allowed to use classes.
So std string is not possible.
Second, if we use pointers,
there is, well there is some
evolution over the past years.
When we wrote the first
edition, so C++ '03,
required that the object you
pass has external linkage.
So you had, outside of
your function, to declare
with extern, a corresponding
character array.
And you could pass that to a template.
In C++ '11, we relaxed the rules.
So now you need internal language,
so you could skip the extern.
But in C++ '17, even
something better happened,
no it's not gone away, you
still need one more line.
But the line can be
directly before you use it.
So you can have no linkage at all,
but you still need an extern,
......a separate object here.
So, an 'l' variant.
And that's the way you
can do it now in C++ '17,
but that means that we have
local visibility of the string
we pass to a template.
Another feature we
have, we can now declare
the template argument, the
non-type template argument
to be auto, and that means
that the type of the non-type
parameter is deduced,
depending on how you declare it.
So if you declare "s"
to be instantiated for 42......
the type of N in S it used to
be in, if it's in character,
it's a character, and by
the way, this is not a trick
to suddenly allow double or so.
It's still not compiled.
And by the way, I didn't try it out
with the class template
argument deduction yet.
That's on my list, I should do that.
We'll skip that.
So here's an application of this.
Let's print all the arguments I pass
to a variadic template.
This is more or less code
we can write in C++ '11
but in C++ ......
but in C++ '11 we would a
need recursive call of print
or print all args.
Here, now, we have what
we call fold expressions.
Fold expressions mean that.....the
......different arguments you pass
are unfolded, so that we apply
the corresponding operator.
It's a left shift or the output operator
to all the arguments we have.
So that means, if we
pass 7.5, hello and str,
we print them.....
sequence with the left shift
operator out to std out.
And the syntax is in
parentheses and with ellipses
in the middle using the operators.
Yes, they have to be both the same.
You can't say on the left,
left shift on the right plus.
But the whole fold expression
ends with the closing
parentheses after args.
The problem here is, if we do that,
we can't have a space here,
and those who saw the talk
about C++ '17 by Bryce.
I think he was asked,
can we easily put here
something in, so that we
have something between
these arguments and the answer
was, 'no, you can't easily.'
But..........................
we have templates.
So let's define a little
helper class and space error.
So which takes whatever
we pass by reference,
and provides the output operator.
And each time the output
operator is called,
well, it prints it out
followed by a space.
Okay?
So, of course, the obvious question is,
well...........................
could I add other space?
And the other question is,
is there a space behind the last argument?
And yes, and yes.
So, by the way, we can
skip the template type
between add space and
args, because we now have
class template argument
deduction in C++ '17.
But we did it back.......
because we can do..... better.....
to say..... let's..... not........
let's be able to parameterize our space
or whatever we put behind the arguments.
And that's for example,
possible by two things.
The first thing is, we
add an additional argument
to the template for the
suffix we want to add.
We could, of course, pass
an additional parameter
which we store in the private member,
and then use with every output.
But this time, it becomes
compile time code.
We don't have any overhead.
But the problem is, of course,
it has to be valid, what
we pass as an argument.
And if we want to have a suffix,
which adds comma space,
we would use the trick I just introduced,
a string, no a string object,
a corrector array with
no linkage, so right defined before,
and passes as a second argument.
Of course, the immediate question is,
is there now, even worse,
comma space after the last argument?
Yes there is, so let's fix that,
because in print all args, we can.....
we can split out the first argument.
We can have special treating
for this first argument.
For the first argument we print
and for all the others, we
print first the separator
and then the next argument.
The other way is not able, possible,
because the parenthetical pack
cannot be in front of the end
So you have to do it that way around.
And the interesting thing is now,
because we use auto here, we can do both.
We can pass a character
or we can pass a string.
So here I use a character,
but the same way I could
define a string member,
a local string member
as I've done it before.
Whoops, where is my code here?
So, here I can say, okay,
here I define my separator,
as a separate object, and then I print it.
This template, with the
ability of taking auto,
is parameterized for both,
dealing with characters and
dealing with string literals.
And that's good.
And there will be applications for that.
To give you one nightmare here.
The feature is called
placeholder types for templates.
And auto is not the only placeholder type.
We have another placeholder
type called decltype auto.
Who's using decltype auto
in his code or her code?
Okay, a few.
Beware, beware, because
one of the side effects
of delctype is that
depending on how you use it,
it might become a reference,
which is good if you need
it, which is bad if not.
So here, you see an example
if s is instantiated for an integer x,
you instantiate the trait
for n.....................
having the value 42
and having the type integer.
Okay?
Now if you pass an object like int.
Here we have y.
And you put parentheses around it.
The rules of decltype auto apply.
And the rules are the same
as the decltype feature
which are not known to everybody,
which means decltype gives you
the type if you pass an entity,
but it gives you type and value category
if you pass an expression.
So what did you do here?
You passed "y" in parentheses.
And "y" in parentheses means
you pass an expression.
So that means decltype auto deduces
to be a plain type.............
for pr values, for l
values a type reference,
an l value reference, and for
x values, an r value reference
So what do we have here?
Why, what is it?
An "L" value, because it has a name,
roughly speaking has a name.
And because we have here a name,
so the type used here
is integer reference.
So we instantiate the template as follows,
n is an integer reference which has,
which refers to the value of y.
So n is a reference to y,
which is a cool feature if you need it.
You could do the same
with another template.
And if you modify y,
you print out, you call
the print function of s,
and by the way, as far
as I know, it can be even
a const member function.
You should try that out,
because the reference is const.
I should add a const here after print,
but print would print
something different here.
Yeah, because it doesn't
modify the object itself.
Let's see who needs it.
Another topic.
Should we pass by value,
by reference or what?
That was a discussion we
had in the author team
again and again.
So the first thing before
you recommend something is
find the facts.
So let's see whether
you all know the facts.
Here's print,
here's a print simply
taking a template argument,
so the question mark should
be replaced by something
using t, capital "T".
And then here, we simply
use it without any further
constraints, or we call type ID which is
available for every type.
So let's implement this to
take the argument by value.
If we pass an int,
that's a very cheap call.
It will probably be in line
so perfectly.....................
perfectly handled.
If we pass a string,
it's an expensive call,
because we copy the string.
We could pass a string reference,
although the template declares
it to take it by value,
by using the std ref, or std c ref hack,
which sometimes works, sometimes not,
depending on what you
need in the template.
If in the template I want to print out x,
I have a problem because
we have no output operator
for a wrapped......
.....with std ref or std c ref type.
And we can pass a literal.
We don't, because we declare the template
to take it by value.
The type of hello decase
to const character stop.
And we can pass a string view.
I always have to say something
about string view now
because in almost each and every talk here
at this conference about C++ '17,
all were praying the new
feature of string view.
String view is a nightmare.
And you will, in a moment, see why.
And we can see a vector of strings
which is, pass a vector of
string, which is pretty expensive
So let's do it by reference
and everything becomes cheap.
The passing an int gets
slightly more expensive
because it's no longer,
not unnecessarily longer the in line.
It depends on whether the code is visible,
what happens inside the code, etc.
Okay.
There's something wrong with
the auto, no, I'm wrong,
there's nothing wrong.....
Here, yeah, I should say something.
Here, remember one final,
one nice change here.
If we take the argument by reference,
print hello is instantiated,
no longer for counts character star.
It's instantiated for the type of hello
because if you pass tendered
arguments by reference,
they don't decay.
So the type, 't' is not
const character star anymore,
it's const character six six
an area of six constant characters.
So there's an interesting
story about that.
This can bite you a lot.
If you deal with strings, recommend that,
recommend that, yeah, a
theme for your test suites,
use strings of different length,
because they have different types.
If in a template you use references,
and suddenly your test suite might fail.
Please do that.
So we had that problem,
by the way, in C++ '98.
We declared make pair to
take a const reference.
And the problem was, if we
called make pair for hi nico
the pair was a pair of
two different types.
And we got crazy error messages,
when we passed this to a map
or tried to compare them, or so.
So this was one of the few relevant fixes
really in the api of things.
In C++ '03, we, yeah, we
decided to take it back
and to pass these things by value.
So by reference is not always better,
to solve this problem.
It just makes it more expensive.
In C++ '11, we had universal references,
so references came back
and the problem came back
because references don't
decay and that also applies
to universal or forwarding references.
But we have type traits now,
so we simply have to declare this as
that the return type is a pair
of the decayed type of the first argument
and the decayed type
of the second argument.
Which, by the way, looks simpler,
because we no longer need type name there
and col and column type in C++ '14.
And this is my favorite function
if I want to say is there evolution in C++
and the standard, yes there is.
So what did we change in C++ '17?
Well, kind of nothing, but kind of a lot.
You no longer need make pair,
because we have now a deduction guide.
A deduction guide says,
you still don't have to
provide the argument types
if you create a pair.
You can write pair 'p'
and I pass hi or nico
and the good thing is,
because the deduction type
takes the arguments by value,
type deduction, class
template argument deduction,
deduces the types to be
const character star,
which is independent from the constructor.
The constructor can still take
the arguments by reference.
So, this is a very nice feature,
and it's a hidden value
ability of this feature,
that we save a lot of makers
and we can separate deduction
from how we pass things in a constructor.
We have another function
here, which returns something.
So let's return something.
It returns plus, the
element's added to itself.
Which is if you pass an int, yeah.
Whether we pass it by
value or by reference,
we should definitely return it by value,
because otherwise we return a
reference to a local object,
which hopefully doesn't compile.
So, the next case I had..................
before was string view.
So if I use string view, and
string view does not have
a definition of operator plus.
Let's define one.
Of course, as we create a new object,
we can't return a string view,
we have to return a
string, but that's fine.
So let's use it, here.
What happens?
The answer is, a nightmare.
You have a fatal run time error,
because.............
string view is worse
than a string reference.
A string reference will
extend the lifetime
of the return value,
and what we return here
is kind of string reference.
String view is semantically
a string reference,
but conceptually, a value type.
That means that we return in double......
....the temporary string.
We can implicitly use each and
every string as a string view
and directly after the statement,
the return string gets distracted.
In our string view, we
return here, when it is used,
uses.................................
freed memory.
We will see this problem,
we will, I promise you.
So........................ again, caveat.
String view is worse than
an std string reference.
Don't use it, if you could
use a string reference.
Don't never use a string
view to initialize a string.
Or in modules, Jason
Turner yesterday said,
if you have a chain of codes,
there should never never be a
string at the end of the codes
when in between there's a string view.
One special thing is,
don't use a string view
to initialize a string member.
Never return a string view,
never initialize a return
value as string view.
But sometimes you can't avoid that,
you saw that, we had a
template defined returning
it's type by value............
and that called......
...............caused a problem.
So looking back on our function here,
the problem is, the return
value is assigned to an auto,
so we don't see that this is a string view
and the reason it is a
string view is because
we deduced that it's return type in double
is the same as the past argument type,
independent from whether it's a value
or whether it's passed by reference.
That will be nightmares,
so how to fix that.
Don't.....never.....
because of string view,
never return in your function,
declare your return type
to have the same type
as an argument type.
Use auto.
Auto would mean that our template
that uses the return type of plus
which is a std string, and
everything would be fine.
And there's another
interesting consequence here,
too bad that Herb is not in the room.
This means, almost always, auto is broken
due to string view.
Because you can't be sure that people do
the................... return auto.
They might return 'T' to see the danger
you should not declare
the return type to be auto
because auto can become a string view.
Yeah?
We're all fucked up with string view.
Sorry, that's on video,
huh, no I didn't say that.
Beep!
(laughter)
Okay, so what is the right thing?
Declare............declare
double to return auto.
Okay, that's safer.
So, but, what if we have
references, universal references.
If you want to forward something,
then of course we should
use universal references.
It's interesting that most of the time,
we think about forwarding arguments,
but not necessarily about
forwarding return values.
So, what happens if we,
if we have a return value
but want to forward it back
perfectly to the caller.
That's fine.
It's valid, even for void.
So the thing below works.
By the way, yesterday in Volgersonne
you help a function
here, that even allows to
forward codes that pass member functions
and the corresponding objects
and an arbitrary number of elements.
But............. auto
loses move semantics here.
So what should we use instead?
Well, you might think,
forwarding perfect... forwarding reference
or universal reference in one direction,
so let's use a universal
reference in the other direction.
Unfortunately, this is a nightmare.
Don't take auto ref ref, although it's....
although it's a universal
or forwarding references,
because you refer to something
that might no longer be there
if invoke gets you a temporary object.
Lifetime expansion is not passed
through a return statement
so that means, you refer to
something that no longer exists.
So, what is the right answer?
Use decltype auto, that's one reason
we have decltype auto since C++ '14.
That's the right way to do it.
That's your blueprint for
a function you want to rep,
a function call you want to rep,
a generic function call you want to rep.
Well, wait a minute.
If we want to use the return
value before we return it,
finally, so between invoke and returning
any call, how do we do that?
Well, again, locally declare your object
as decltype auto, not as auto ref ref.
You have the same problem again as
I just explained to you, only indirectly.
So, but there's another problem here.
Decltype auto is not allowed to be void.
So, what do we have to do?
We have to separate between two cases,
thus the function we call
returns something or does it not,
which often is logically anyway a thing
you might do, but not necessarily always.
So, what you have to program is this.
If constexpr is at a compiled time,
we decide which thing to compile here.
We check what the resulting
type of an invoke is here,
we have a new type trait
for that, since C++ '17
which is called invoke result t,
and we check whether
the result type is void.
If so, we call invoke
and we return nothing.
If not, we call, we use a
decltype trait to return it.
That's your blueprint
for generic function,
perfectly forwarding the arguments,
perfectly returning the arguments,
and still being able to deal with both,
the pass arguments and the return value.
It's surprising me that nobody,
almost nobody knew that.
I even asked my co-authors
and a couple of people
in the standard committee,
and they said, oh, ah, wait, um hm.
They said, usually the answer was,
well you can use raii
so that the destructor
of your local type does what
you want to do after the call.
That's the way we think in C++.
I'm an old guy, I want to program
straightforward sometimes.
There's another problem.
Some people prefer to put
parentheses around return values.
Don't do that anymore.
Don't do that because
this is decltype auto.
You remember, decltype
auto behaves different.
If you pass something
that is not an object,
but an expression and you
have made it an expression,
so ret is an 'l' value, so
because it's an 'l' value,
the return type will be the
type with one ampersand.
So an 'l' value reference, which means,
if the function you call
returns a temporary,
you will again return a
reference to a local temporary,
which is also a nightmare.
So a lot of flaws are here involved.
Okay.
No parentheses around return statements.
Put that in your style
guides, please, do that.
Okay, and I showed something regarding
if constexprs were here just to make sure
you know what happening.
If constexpr does not
mean that the so called
discarded statement, the
statement that is not used
the if of the then path is
not...........is not checked,
it's checked against everything
that is not dependent
on a template parameter.
So if you have an undeclared call there,
even this is not compiled
or translated into
into assembler code, it is checked
whether it's possible to call undeclared.
If not, it will fail,
only if it's depending
on a template parameter
it will not fail.
And the same is true for assertions,
even if the else part is not used,
an assert fault will fail,
unless it depends on your
template argument, okay.
I have to say here, that
this is not a problem
for Virtual Studio users, yet,
because they don't have
this distinction between
definition time and instantiation time.
They defer everything at definition time,
through the checks I just
mentioned to instantiation time.
So, they are about to change that.
Since Visual Studio 2017,
15.3....... which was as far as I know
was shipped at the
beginning of this month,
you are able to turn
this feature off or on,
depending on how you like,
with /permissive minus
and then your program behaves differently.
You should know that.
Okay, final slides.
Let's talk a little bit about variadic.
Variadic is so cool, we had it before
when we printed all arguments.
So here's my example at the end,
to show you, that.......
there's a reason I still don't understand
all the pages in my book.
(laughter)
This is a base class.
The base class uses, well,
it's just using a type, 't'.
We can define a derived class,
deriving from several base classes,
and we can now have using declarations
deriving all the constructors or
deriving all the assignment operators.
So that means, if you
have something like this,
a multi of in std string and void star,
you get an, a hierarchy like
the one on the top right.
Okay, I skipped details,
because there is something worse
or better and that is, you
can nest variadic templates.
Did you know that?
I didn't know that, until I
read that chapter in my book.
(laughter)
You can declare a class variant to derive
from a variant storage, this is variadic,
so you have a couple of types.
And so you're deriving
from all of these types.
It computes the storage and the reason
we might do that might be crtp,
if you have heard about that.
But now, look at the next line,
no that means if we have a
variant int double string,
it could use this, that you
derive from these three,
from a variant of these three types.
Now let's see the second inheritance,
or the second specification
of a base class.
Here, we have twice variadic templates.
We have nested pack expansions,
because want to have, we have that,
we have for each type a base class
and in that base class,
we have all the types.
No, so we have twice,
dot dot dot, look at that
and that means that this
expands to having three more
base classes and in each base class
we have again, as a first
argument, place our own type
and then all the other, all the types,
because that we use in the implementation
to find out which is the index of our type
in this base class or in this type.
Scary?
I don't know, cool.
And you can use, using declarations,
you can say, I want to,
when I destroy code,
I'll destroy functions
of all my base classes
using fold expressions, etc.
I like C++ and I hate it.
(laughter)
So let me summarize.
These were probably my biggest findings,
concerns, or those I can tell you
and even I understood them, maybe,
the last one, a little bit only.
What we see is C++ is tricky.
It can do everything, but
you can make every mistake.
By the way, thanks to all contributors,
it's not for the mistakes, that's thanks
for all the things that we got.
Templates are tricky.
We wrote the book.
You might see almost each and
every example of this there.
And you saw a lot of things in C++ '17
and the good thing is I kindly
also finished a first draft
of a book about C++'17
covering all the features
in not only the template features,
and this will be available
as an (inaudible) project,
that means it will be available
with the first 130 pages
pretty soon, in the
next four or five weeks.
And you can buy it for a very low price
and then get all the updates for free.
And the more content is
in, the more expensive
the book will become.
So if you are interested,
because I do it for a living now
reach us at the website
or look at the website.
And by the way, I'm still
looking for reviewers,
but please don't register all.
I might then say, I have enough.
And finally let me
thank, compiler support.
Let me thank all those
guys who made this happen.
All examples I showed
you, already compiled in
at least one compiler.
Thank you very much (claps),
all the guys for that.
(applause)
That's it, thank you very much.
I'm here, available the whole week.
I'm still available to talk about some,
how to teach C++, you see how bad it is,
and some book signing.
There will come new books there.
I told them to prepare for 100 books.
They didn't trust me, okay.
Now tomorrow, there will be more.
And that's it, thank you very much.
I'm here, available for questions.
Thank you very much.
(applause)
