- Hi, I'm Simon.
I'm gonna tell you to write
well behaved value wrappers.
Before I go in to define those terms,
at Microsoft, we have this survey which
would be great if you
could fill in and you
could win an Xbox One, yay.
Yes, thank you.
Okay, with that out of
the way, value wrappers.
What do I mean.
What I mean is types with
value-semantics which
can store objects of any type.
So, some examples of this
are std::pair, optional,
variant, all from the standard library.
Some of you may be familiar
with Jonathan Vaccaro's
main type library which he
uses for his strong type posts.
And by value semantics, I
mean you copy the wrapper, you
copy the value.
If you move the wrapper,
you move the value at,
kind of like this was just an
end of something like that.
Well behaved.
I have two criterial I'm
particularly interested in.
The first is one which
hopefully we're all interested
as C++ programmers.
We want our code to be
fast and value wrappers
can have some overhead if you
do not write them correctly.
So, I care about performance.
I want everything in this
talk with either focus
on performance or on being unsurprising.
Now we all care about being performant,
but it seems like a lot
of us in C++ do not care
about being unsurprising.
Well, I do, so I want to make
these wrappers unsurprising.
We don't want all of
these crazy edge cases
which we had so often in C++ world.
A cautionary tale.
I find this comment in the source code for
a huge program, one
which I'm actually using
to present these slides.
It says, "note, GCC has
missed optimizations
"with Maybe," the type is
talking about in the past,
"and it may generate extra
branches, loads and stores.
"Use with caution on hot paths.
"It is not known whether or
not this is still a problem."
If the people who wrote this
comment are watching this talk,
either in this room or
later, then hopefully you
can apply some of these things
I'm gonna be talking about,
so you can get rid of this comment
and actually fix the problem.
Comparison operators, starting
off with something we're
presumably all familiar with.
This is about the simplest
wrapper type I could think of.
It takes a template
parameter, stores it inside.
You might want to write this code.
We have a wrapper of int
and we like to be able
to compare ints, so may we want to be able
to compare our wrappers as well.
In some cases, we don't.
In a lot of cases, we do.
And this will not compile
unless we do some extra work.
So, we can go ahead and do that work.
After noting that this is absolutely
not surprising behavior
for many cases, we want
this to compile.
So, if we start with this
blank slate, go ahead, write
our comparison operators.
Might look something like this.
(laughing)
These are all the comparison operators
for my implementation of std optional
and this is pretty much
exactly what you have
to write if you want to implement this.
There's no getting out of doing this work.
You can implement some
in terms of others, sure,
but eventually you'll have
to write all these overloads
and it's not gonna be pretty.
So, now if we write all that, it compiles.
So, we are unsurprising but
it's so much code and we do this so often
and it just makes me so sad.
Has everyone seen this film?
Lego Movie, yeah, great film.
My favorite character in The Lego Movie
is this guy.
(laughing)
Loves his spaceships.
He particularly likes this one.
In C++, instead of looking like this,
our spaceship looks like this.
So, this is the new spaceship
operator from C++ 20
and it's gonna solve some of our problems.
It goes something like this.
We have two objects, A and
B, which we want to compare.
We do the spaceship
operator on them and compare
the result against zero.
That's gonna tell us that
it's greater, less than
or equal to the other one.
You don't tend to actually
write the spaceship operator,
but this is kind of how it's defined.
So, if we have pair, this
is not exactly std pair.
This is just like a simple
pair and body wrapper type.
You can actually get the
compiler to do essentially
all the work for us.
We write = default, then
the compiler will generate
comparison operators which
lexicographically compare
by member, so we will
first compare by T and
then compare by U and it
essentially does what you want.
Jason.
(audience member replies)
Is it automatically const std per.
I can't remember off the top of my head.
I would hope that it would be.
Maybe you have to write
that on the declaration.
I'm not sure, I can check afterwards
and get back to you.
So, this generates code
is kind of like this.
You don't have to worry so much about
those decltypes and common
comparison categories.
But of course, this being C++, it doesn't
just return an int.
It returns something
a lot more complicated
which would be an entire talk on its own.
But you can see, it's generating code
to compare T and to compare U.
So now we've gone from all this code
and with the power of
spaceships, we've gone to this.
This is code I stole from
Barry Revzin's blog post.
But this is so much easier, yes, Jason.
(audience member comments)
Right, I do have constexpr
there, you're right.
So, that's probably what we do need for it
to be constexpr.
So now we've gone from being unsurprising
with a ton of code to just
being plain unsurprising.
This is a huge win.
That's enough for comparison operators.
I'll move on to noexcept propagation.
So, noexcept is used quite
a lot by some people.
Not so much by others.
If you have one piece of advice you want
to follow, it's make
sure that your propagate
whether you move operations
and your swap operations
and noexcept, because this matters.
For swap, people will
write code which assumes
this thing can never throw.
People use the copy and
swap idiom for example.
They will do this thing
and if it throws, you're
gonna break someone's code.
Don't do that.
For move, it has even more worth because,
say we have this vector of
optional and this tracer type
is just something which keeps
track of copies and moves.
If we propagate noexcept correctly,
then we get a hundred
thousand copies and more
than 200,000 moves.
But if we don't do this work, then a bunch
of those moves turn into copies
because std::vector
internally will not move
the underlying objects if it could throw,
'cuz then it could move
something, it throws,
the data's lost.
We hate data loss, so
vector doesn't do that.
And you do this by, say
we have a move constructor
for optional here.
You don't have to worry
much about implementation,
but we can do something like this.
Add noexcept and check
if the underlying type
is nothrow move constructable.
If it is, then this is noexcept.
If it's not, it's not.
So, this does the work for us.
You may be familiar with this quote.
"To explicit, or not to explicit,
"that is the question."
And that indeed is the question.
Say again, we have our wrapper type.
This would be a compiler error.
We have a wrapper of int.
We try and initialize it with an int.
Seems reasonable.
But it doesn't compile.
So maybe we say okay, this is fine.
I'll write constructor which takes a T
and this is fine.
Obviously, some of you are super smart
and you'll be, ha, yeah,
we use perfect forwarding.
Feel very happy with
ourself, very pleased,
but then, someone comes along and writes
this very aptly named type, oh no.
This has an explicit
constructor from bool.
So, then if someone writes a function
which takes a wrapper of oh
no, then we can pass true
and we'll implicit the
convert, even through
the author of this type
presumably put explicit
there for a reason, oh no.
So, this is surprising.
We don't want this.
So, what we usually do at the moment
is you write two overloads
and we use SFINAE
like std::enable_if to
select between them.
See, don't worry if you
don't understand enable_if.
It essentially means that this version
will only be a candidate if
those things evaluate to true.
So, we have one version
which is not explicit
and we have one version which is explicit.
And the only thing we switch is this.
It's convertible, so if the
underlying type converts
implicitly, then we convert implicitly.
Otherwise, we don't.
And now this compiles and it's great
and this doesn't compile,
just as we hoped.
So, we're unsurprising, but
with code duplication.
So, this isn't great.
We have noexcept bool.
We mentioned it literally
in the last section.
What if we could have explicit bool?
This would solve our problem.
Instead of having two overloads,
one which is explicit, one which isn't,
we just move this is_convertible thing
down into this explicit specifier
and this does the same thing.
There is just one overload.
The code is clearer or
some definition of clear.
(laughing)
Now we've gone from unsurprising
with code duplication
to just being unsurprising, success.
And this explicit operator
is actually in C++ 20,
so if you happen to use
C++ 20, then, oh great.
But also you can use this.
(audience member replies)
Yes.
(laughing)
If you have a time machine,
Gashberg said, he can use it
and that's true.
Okay, conditionally
deleting special members.
Now, we're getting into
some Archean stuff.
So, if we have simple
indication of optional here.
It's essentially just a union
of T and some dummy type
that we don't care about and then we have
a bool in to say whether
we're engaged or not.
So if we have a copy
constructor like this,
it's just kind of normal copy constructor
and we have a normal
destructor which checks it
as engaged and then destructs,
then we have to be very
careful because option
is not a type where you can
just default your operators
because it might have
to delete, it might not.
It might have to copy, it might not.
So, in cases where you
do have to write your
own copy constructors
and your own destructors,
you need to think about whether you have
to conditionally delete
your special members.
So, this is not a very good reason
but it's part of a reason as
you get better error message.
In the first case where we don't delete,
then we kind of get
something which is internal
to the implementation
rather than just saying
what went wrong.
We had a deleted copy
constructor optional.
More important is if we want to check
if something copy
constructable, say we want
to SFINAE on this, say we
want to do a context for if,
we would get the wrong
answer if we don't propagate
our deletion because it's not
SFINAE friendly, essentially.
If we do not do this,
we get the wrong answer
and this can lead to surprising errors.
Your code will hard compiler
error even if you think
that it shouldn't.
So, this is surprising again.
So again, back to our simple
optional implementation,
what you actually kind of
need to do at the moment
is you make a base class.
You shove all of your
details, all of your members
into this base class and
then you privately inherit
from this class and you
privately inherit from
something which will
delete your constructors
and your assignment operators.
You kind of have to do
this with inheritance
just because how deletion of
special member functions works.
And so this, these
delete_ctor_base and assign bases,
they look something like this.
They have a class which will check if
it's copy constructable
and move constructable
and then we have a specialization.
So, if these are both true,
then they're both defaulted.
If it's true and false,
then it's default delete.
Delete default, delete, delete.
Make sense?
Just dealing with all the different cases
and we do have to write
all that code each time,
even though it's essentially the same.
(sighs)
And then we do the same
for delete_assign_base.
And so this will now do the right thing
if we try and check if
it's copy constructable.
So we're going to be unsurprising,
but a ton of code again.
Maybe seeing a pattern developing here.
Concepts would help a lot with this
because we can actually
constrain nontemplates like this.
We can say we require,
it's copy constructable
and require our move
constructor, the underlying type
is move constructable and
then do all our work in there.
And this is great,
'cuz we've gone from being unsurprising
with a ton of to being unsurprising again.
Triviality propagation.
Who's even familiar with the idea
of trivial copy constructability?
Maybe about a quarter, a third
of the room or something.
Okay, so this is getting a bit weird.
This is the System Five,
System Five, System V.
I can never remember.
The holy tomb.
A quote, "an object with either
"a non-trivial copy constructor
"or a non-trivial
destructor cannot be passed
"by value because some objects much have
"well defined addresses."
Now, this isn't meaning
like quite C++ pass by value
but it does mean putting it on a stack
versus passing it in registers.
Passing in registers is copying,
is passing by value in
this case because you're
just copying it into a register.
So this is important.
So, trivial copy constructor
is defined as this.
The copy constructor has to be defaulted
or implicitly defined.
There's some extra wording in there.
I kinda want to keep it simple.
Can't have any virtual member functions
or base classes and all of the base
and member copy constructors
have to be trivial.
Kind of like you might expect.
And it's very much the same
for, yeah, sorry, question.
(audience member replies)
No virtual base classes, yes.
So, the question was is
it no base classes at all
or not virtual base classes.
Just virtual base classes.
And the trivial destructor
is essentially pretty much similar.
So, we could actually have a static_assert
which says it checks if my optional int
is copying, trivially copy constructable.
Then currently, this would compiler error
which means, because what the ABI says,
we cannot pass an
optional int in a register
because this check fails.
Similarly for it's trivially
destructible, same thing.
And this is not performant.
(audience member replies)
Pardon?
(laughing)
Mikhail says it's not a word and sure.
It is for the purposes of this talk.
I define what is purpose.
Okay, so this is some assembly
code produced courtesy
of my Mike Oldbolt.
As you can see, if I do not propagate,
then I essentially pass
things on the stack
and if I do propagate triviality, then
the compiler just works out what value.
In this case 42, because of course,
and it just moves it into a register
and just works.
Yes, even worse, if I start to pass this
as a function argument, you see this
is starting to be more
code using stack more
and then if I don't propagate
trivial destructibility,
then it gets even worse
and then even worse if I use vectors
and this is not so bad.
It's essentially an unruled loop
destructing all the underlying objects.
You can see how this kind of thing,
when we're just trying to use ints,
it's not great.
We want this stuff to be fast.
So say we have our destructor here.
What a lot of people say, oh, fine,
we just use SFINAE.
It'll work.
No, it does not work.
You cannot have destructors
or copy constructors
and move constructors which are templates.
So, you can't SFINAE them away.
Them's the rules, I don't make them.
What you can do is make
base classes again.
Private based classes are
used all over the place.
If you ever look at standard
live implementation, you
will see these and so we
do our actual destruction
and our base class and
this version is for when
our thing is not trivially destructible
so we have to call a
destructor and then we
have another case where we
make a template specialization
for when we do not have to
call and we just default it.
And then we do the same
with the copy constructor
and with the copy assignment
and the move constructor
and the move assignment.
C++ is great.
(laughing)
Yeah and then we inherit from,
these all inherit from each other
and then we inherit from the top level one
and it's all fine.
We just default our destructor and
the base classes will work out what
to do essentially.
So now this compiles and this compiles.
So our performant, can anyone tell me
what comes next?
(audience replies)
But with lots of codes.
There we go, starting to catch on, okay.
How do we fix this?
Well, we can actually use concepts again.
We do need this other paper written
by Robson and Andrew Sutton, I believe.
It's being discussed yet.
I think it's going to be discussed
at San Diego, but it essentially means
that it would make this work.
Currently, just because of some wording,
it's not quite there.
Hopefully we'll get this and it will
all be great and we can
go from being performant
with a ton of code to just
being performant again.
(audience replies)
For five more seconds.
This one, so you can have a look, oops.
There you go, yeah.
So, you just say we
require copy constructible
and move constructible
and it's, I can't remember
exactly the wording in the
paper but it means that if
these things are removed,
then you retain triviality.
Yes, Mikhail.
(audience member replies)
So, the question was, is the problem,
(audience member replies)
yeah, so the question's
about the current wording.
I can't remember exactly.
I think the current wording means that
even if they're removed, it's not trivial
and this paper makes it
such that they are trivial.
(audience member replies)
Yes, that works for destructors as well.
Okay, I noticed JF is in the room,
so I stole his tweet.
How do you know if a C++
developer is qualified, JF.
(audience member replies)
You check their CV.
(laughing)
I did not plan for JF to be in the room
but this is so much better.
(laughing)
Another answer would be you look
at their references.
- Ouch.
- Yeah.
And this is what I'm gonna talk about now.
Ref-qualified accessor functions.
Okay, so this is what lot
of code I see looks like.
You know, you have a, you want to write
an accessor function.
It looks kind of like a dref.
This would be what you
might write for optional
of something like that.
You want, this could just
as equally called get
or value or whatever.
People have different names for this.
You'll see this a lot.
People will have a non const
version and a const version
because you want it to
work in both cases, right.
This.
Can anyone tell me
whether these lines copy
or move with this operator?
Copy.
First one copies, second one moves.
'Cuz in the first example, we move
and then we take, use our operator,
but our operator doesn't care about
whether it uses it's call on an L value
or an R value, whereas
the second example, you do
the D reference or whatever
and then you do the move.
So this is always going to be an R value,
so you'll do the move.
This is not performant or unsurprising.
So, what we can do is
instead of just having
a non const and a const
version in C++ 11 onwards, we
can add reference qualifiers.
Yay, multiply the amount of
code we have to write by two.
(audience member replies)
Const&&.
I have a slide on const&&.
(laughing)
Oh dear, his response, yes.
Very much, oh dear.
Const&&.
Here's one reason why
const&& might actually help.
So, if you use something like std,
(laughing)
let the audience compose themselves.
So, if you have something like std::cref
which creates a const reference wrapper,
this actually has the
const&& version deleted
so that you do not actually
accidentally make references
to temporaries because
it will just be deleted,
be destroyed and will
be undefined behavior.
Yes, so with our const&&, you get
whatever your favorite
undefined behavior metaphor is
and if you do do the const&& version,
then you get a proper compiler error.
So, I mean, it's an edgy, edgy, edgy case,
but you know, it could
be worthwhile, Gashberg.
(audience member replies)
Right, so the comment was, if
you have universal references,
sometimes it will just
get deduced like that.
Especially if you're in a generic context
and you have a bunch of
things which are calling
each other and returning from each other
These things turn up.
So, it's not like you're
just gonna be std moving
a const thing, because that's weird.
But this does turn up when
you add universal references
and generic code.
Thank you.
Yup, so we have this
and we're now performant and unsurprising,
but with code duplication.
Yes, code duplication,
but we could have deducing this.
This is a paper which
myself and Gashberg and Ben
and Ben Dean and Bryon
Robson are working on.
The idea is that you deduce the type of
the implicit object parameter.
This might look at little
bit weird at first.
If you're used to Python
where self is like
an explicit parameter to member function,
then this maybe looks a little bit
more reasonable to you.
So we have qualify that this parameter is
the implicit object parameter and then
just use universal references to deduce
what the type is and I
have a feeling Gashberg
is gonna tell me this
code is slightly wrong,
but I don't care.
(laughing)
(audience member replies)
Yeah.
(laughing)
Yeah, so we could do this.
Does anyone have any
questions about, Jason, yes.
(audience member replies)
Does it require decltype auto.
Sure if you want to return
a reference, then....
(audience member replies)
Yeah, yeah, you're right.
This should have been
decltype auto, not auto.
Thank you, Jason.
(audience member replies)
Yeah, okay so now, we've gone from
being performant and unsurprising but
with code duplication and we've got rid
of all of that.
Okay, I wasn't sure whether
to include this or not.
SFINAE unfriendly callables.
This stuff is hairy.
I included this purely for the people who
in the room maybe just
knew everything I talked
about and you might still
learn something from this one.
Here we go.
Right, we have our wrapper.
Return of the wrapper.
So then we have this, these
member functions called pass to.
Now these take template, these
are gonna take like lambdas
or function objects or whatever you want.
It's just a template parameter.
Don't worry about it being by value.
And we have const and we
have non const overloads.
And this is important
and then we just have
a decltype because we
want to, if this call
is not valid, we want to SFINAE out.
This is useful in many contexts.
So we have this code.
Familiarize yourself with
it, it will be important.
Non const, const overload,
SFINAE friendliness,
using the decltype, calling the function.
Okay, everyone got that?
Okay, then we have a wrapper of a foo
which just has a non const member function
and we pass in a generic
lambda which we'll call
do thing on X.
This seems like reasonable-ish code.
Again, for some definition of reasonable.
Having to make these lambdas
is super annoying in C++,
but we have what we have.
So, we have a wrapper of foo.
Foo has a member function,
a non const member function, important.
We pass a generic lambda
which calls this function.
Still with me, yeah.
Compiler error.
Don't try and read this.
I will explain what it says.
It says passing const foo as
this argument discards qualifiers.
Where did const foo come from?
(audience member replies)
The const overload, exactly.
So, there's no const in this
code at all that we can see.
We don't have a const wrapper foo.
If we did, this would
make sense, more sense.
If not a wrapper of const foos.
We don't see consts on this slide,
but there is const here.
What happens is these are both candidates
for overloads which
could be called, right.
They're both called pass to.
Arguments look okay.
So, what we do is we check the decltype.
We want the decltype because we want
to be SFINAE friendly
and so we check the
decltype in the first one
and it says, okay, we return,
I think it's, void, yup.
We return void.
The second one, we try and
work out what we return
and we hit compiler error
because this called to X
don't do thing where X is const qualified
is not valid because our member function
is const unqualified.
See what I meant about hairy?
So, just to explain in
slightly different words,
the fact that we have a
const and nonconst overload
and they're both candidates
means that these decltypes
have to be checked and
if the decltype does
something invalid, which is
not in an SFINAE context,
then you're gonna get hard error
and there's fundamentally no way to get
around this in C++ today.
Both having SFINAE
friendliness having const
and non const overloads,
no way to get around it.
This is surprising.
This bug took me days to work out one
of my option implementation
was having some bugs.
It's horrible.
So, we could do is get
rid of the decltypes
and this works because of how auto works.
Essentially, auto is not worked out
when you try and instantiate the signature
of this function.
It's only when the body is instantiated
That should have had
return FTs in there, but.
Yeah, so we could remove the decltype.
It will compile now, but we
lose the SFINAE friendliness.
So now we don't get compiler error.
If you just look at this
and think, oh, I removed
the decltype and suddenly
it fixed everything.
Like what in Earth is
going on with this language
and this is a very valid question.
(audience member replies)
You can forward no except here.
Yes, you can't forward
no expect either because
it would have the same
problem of trying to
something SFINAE unfriendly because it has
to instantiate the
signature of the function.
So now we're unsurprising but we're
no longer SFINAE friendly
and this can be a problem.
Some code relies on your
stuff being SFINAE friendly.
So we would like this and
it's literally impossible right now.
But we can use deducing this again.
Turns out we accidentally
solved this problem
without really realizing.
We did this paper and then
Augustan Berris, I don't know
if I pronounced his name right.
He said, oh yeah, this solves my problem.
I wrote a paper on this
and this actually fixes
the decltype issue.
So, the reason is because
this is a template
and it's gonna deduce whether it's const
or non const.
We're not having to
instantiate invalid decltype
and types when we're doing
our overload resolution.
It'll just say, okay,
well, it's const qualified
or it's not const qualified, so that's
the only candidate.
The other one's not even,
not even checked and that's kind of how
this gets around it.
It's also, this is only a
problem with member functions.
If you have nonmembers, then
we don't have this problem.
And now we've gone from unsurprising but
no longer SFINAE friendly
to just unsurprising.
These are all the thing
which I talked about.
I hope that you've come
away with some ideas
of how in your own libraries or maybe
even problems you've
seen in libraries you've
used, such as I mentioned at the start.
How you could solve
some of these problems.
There are more kind of topics and things
that I could cover in
there, but these ones
are important for being unsurprising
and for being performant.
And that is how to write
well behaved value wrappers.
Thank you.
(applauding)
I have some resources here,
if you'd like to check them out and I
will now take questions.
Lots of phones, no hands.
Hand, yes.
(audience member replies)
Great, yeah, so the question was
and whether I can comment for the kind
of libraries which are used by a lot
of developers like Boost, how many
of these kinds of topics
are actually applied
and which ones could be
applied to make it better.
So, but for Boost Optional
and things like that,
as far as I remember,
they do all of this stuff.
Same for all the standard implementations
of optional now, C++
didn't propagate triviality
for awhile, which meant
you got some bad code
in cases but he fixed it.
And so standard versions,
the Boost version
are all good.
In fact, a lot of these ideas
I got from reading Boost code
or standard library
code, things like that.
Things like that comment
which was at the top.
If you look at libraries which are used
by, written for major
projects but which don't aim
to be high performance generic libraries
in their own right,
like LVM, like Mozilla's
kind of standard library type thing.
Those don't tend to
follow these guidelines.
So no, LVM's Optional does
not do a lot of this stuff
and Mozilla's maybe doesn't
do a lot of this stuff.
I'm sure they're many
many other cases which
are the same.
Unless there's a quick
follow up, I'll take Mikhail.
Okay, Mikhail.
(audience member replies)
Okay, Mikhail's comment
was that I was incorrect
about the no except having the same issues
but then decltype which
was Gashberg's question
and the reason is that
no except similarly to
when I mentioned auto and
how it only evaluated once
the function body is instantiated,
it's the same for no except.
It doesn't happen when
you're instantiating
the signature and doing
overload resolution.
Is that correct, Mikhail?
- Yes.
- Thank you, Gashberg.
(audience member replies)
(laughs)
- [Gashbeg] The first time that
you said that concepts solve
this problem, there was some slight, I
didn't understand how.
- Wee.
- [Gashberg] The second time you had
an additional paper cited,
- Yes.
- but the first time
you didn't.
- Yes, the difference
between, so this is the concept + P0848.
And, jeeze, I have a lot of slides.
Must be soon.
Oh yes, this one.
- Yes.
- Yes, so the difference
is the current wording
will in deletion works
if one of these is SFINAE,
well, not SFINAE'd out,
but it would be equivalent,
like if the requires clause
resolves to false, then
this thing will be deleted.
That's in the current wording.
It's just, it won't be trivial
and that's the thing which is missing.
The deletion works today.
The triviality requires the other paper.
(audience member replies)
No, but it--
(audience member replies)
Okay.
- Yes.
Otherwise, it can be
trivial and deleted, yeah.
It matters in different cases whether
the underlying type you're wrapping has
them deleted, trivial or non trivial.
There are three cases.
- [Audience Member] Okay,
so optional is complicated
for many reasons, but in one
of your first slides, you
had like a pair.
- Yes.
- And I find myself often
lately thinking when
I'm working on something
that's a simple wrapper, wrap around int,
like you had earlier also,
I'm saying, you know what,
all of this would be much simpler if all
of the numbers are public, as it is here,
and I have no constructors.
- Yes.
- Then you don't have
to worry about any of these questions.
- Right, so the--
- I just wanna hear comments
on that thought.
- Yes, I mentioned
that briefly in the talk.
Maybe I didn't, yeah maybe
I wasn't clear enough.
So some of these things only matter
when you have to write
your own constructors
and destructors, like for optional.
- [Audience Member] If you are writing,
if you personally are writing a class like
that today, you just leave it
like that.
- Right.
- Okay.
- And for depending
on what kind of things you want
because the comparison operators
and the explicitness still matter.
'Cuz if you want implicit conversions, you
have to do more stuff and if you want
the comparison operators, you have
to do more stuff, but you don't have
to do all the nonsense with,
like propagating deletion
and triviality.
That just works.
It's only if you have to write your own.
(audience member replies)
Today we can mix them and you mean as like
a private based class or something?
(audience member replies)
Right, right, yeah.
So, we could inherit
from that and then mix
in all the operators
you want, which is kind
of, it's a similar
technique to how I deleted
all the members by moving
this into a base class,
inheriting from that and
then inheriting things.
We deleted it.
You could inherit from
things but define operators.
Yes, thanks, Peter.
Mikhail.
- Alright, I have
a question that's not directly related
to the topic of the start but to something
that appeared on the slides.
Can you go to the assembly slides.
- The assembly slides.
I will have a shot.
I won't have any jokes while I'm---
- [Mikhail] I'm just trying to understand
why the code in this and
the thing that it does.
- Yeah, which one are you
particularly interested in?
- Whichever got note,
- Okay, here we go
- the last one.
- Okay, this is
the simplest case.
- yeah, why did it move
the absolute of the operand.
- Because this is an optional int,
so it has the int part and the bool,
so this just happens to
be the byte representation
for encoding 42 and true.
- Oh because
there is a high bit and
that represents the bool.
Okay, that makes sense, thank you.
- Yeah.
- [Audience Member] Just a
sort of followup question.
It seems like if we fast
forward through the slides
(laughing)
to the last statement, but you know most
of what the six or seven departments
what would constitute a good
value cemented wrapper class,
- That's right at the end,
so I can find that one.
This.
- Right.
It seems like this would
be a great example case
for a concept in C++ and be able to say
this is a correct semantic
value wrapper class type.
Is that something you
thought of or plan to add
as a concept for C++
20 to be able to check
all of these at comply
time as we laid out here.
- Yes, that's an interesting question.
Because these are all
related to instantiations
of types.
You care how it works for optional int
versus optional std vector
or something like that
and checking behavior
between instantiations
is not really something
which concepts map to well.
Did you have a comment, Gashberg?
(audience member replies)
Sorry, could you use
the microphone please?
- Oh sorry.
- So, I don't have to repeat.
(laughs)
- [Gashberg] This list is
effectively about encoding
all of the subfamilies of the elements
of programming regular requirements.
So, because that's what we think of
when you say wrapper type,
you're basically saying we're
gonna forward all of the
sub things of regular
that you can put in the
interface, including comparison
because that's what ideally that happens,
unlike our
(mumbles)
Unfortunately, regular does not mean
(rustling of mic drowns out speaker)
in all of the screening this is in
all of the various Boolean things.
Like, explicitly for instance.
That stuff is not in been reckoned,
so, when you have a family of concepts
that is literally two feet of power of
things that you have over there.
This is how many concepts
there are up here.
So, if you could write probably something
like 60 different concepts based on this.
Whether you have no except swap or not,
those are two different
concepts for our families
really because every one of those can have
an explicit constructor or not.
(laughing)
Yeah.
(audience member replies)
Yes and that's what I'm talking about.
This is not one concept.
This is a family of
concepts, so what Simon
is really talking about is up there,
a giant decision tree that we're trying
to encode into exactly,
this is the concept we want
but it's like regular
parameter, but all of this bool.
- So, if you tried to
plot this on a graph,
this is not like a line
of do I fulfill this thing
or do I not.
It's like a line for
every single one of these
and you exist somewhere on
this nth dimensional plot.
- [Audience Member] Right,
I think my own point is
this slide and the list of thing seems
sort of purposeful
exemplar for what concepts
are supposed to provide conceptually.
You're supposed to do with
total compiler, I want
this to have
(mumbles)
I remember at such schools
and trivial propagation
and blah blah blah, but
then characteristics
that you want.
And that compiler would
be able to check that
and tell me that
(rustling of mic drowns out speaking)
you don't have this
like you think you like.
- [Audience Member]
Yes and you could write
a concept that was parameterized by a bool
for every one of these dimensions.
You could say, I want
my type to fit regular
true, true, false, true, true, false.
- Okay.
- Yeah, the common way
is we could have
essentially something that
is parameterized by all of these
and say, okay, I'm interested
in these parts and--
(audience member replies)
Since you're next to the
microphone, could you, thanks.
- [Audience Member] Four
or five common use spaces
that are typed at this concepts
for the common use cases
and then everybody could
say, I think my type
is, has these values and
that common use base,
so I probably just require this concept
and boom, I get composite
and checking for everything.
- Yeah, you could do things like that.
I think,
if I was to use something like concepts
for things like this, it would actually
be more for testing.
So, if I rule out a list of requirements
that I wanted my generic type to have
and then I could have tests which run
at compile time and check that I actually
have implemented triviality,
propagation correctly
or no except propagation.
I can have those checked in my test suites
so that, because a lot of this stuff
is kind of subtle, so
maybe if you don't know
what you're doing and you
make a small change, you
might affect some on this.
So, you could have, you could have testing
to actually check some of this stuff.
That might be useful.
Any other questions or comments?
Yes, Matthew.
(audience member replies)
Yeah, so the question
is if I'm implementing
from a standard library implement or
a Boost implementer or something and,
'cuz I had examples that start like pair,
to pull, variant and all of those things I
would want to fulfill as
many of these criteria
which are applicable, so
some of this stuff you
can rip out.
So, for example, the deletion of
the special member functions.
You can have one implementation of that
which takes Boolean template parameters
and you would say for
example for optional, you
would just check if T
is copy constructable
or trivia, could be
constructable, moveable, whatever.
But for expected, you would check T and E
and you would just pass these
in as template parameters.
Template arguments, even.
And then you could reuse all of that code
which I had the four different things
on the slide, that would all just work.
And that is what standard
libraries tend to do.
They have one version
which takes bool parameters
and then they reuse it.
So, things like that, you can reuse.
Off the top of my head,
some things, like if you wrote your types
in particular ways, you could use mix ins
for the comparison operators
like Peter mentioned.
For no except propagation, probably not.
So yeah, it depends on the topic.
Some of these yes, some of these no.
That answer the question, cool.
There was another question
over there I thought I saw.
No, good.
Okay, thank you very much.
(applauding)
