- So welcome everyone
to my third talk here.
I'm Arthur O'Dwyer
and this is a talk that I called
Concepts as She is Spoke
because when I
submitted this talk to CppCon
the reason I submitted it was
because there weren't at that point
very many talks talking about concepts
even though it's now in the working draft.
Now it turned out, part
of the reason for that
is that all of the keynotes
were about concepts.
So this is not gonna be a keynote.
This is gonna be sort of an
antidote to Bjarne's keynote,
which I highly recommend
you should all go watch,
you should all have seen,
in which he was talking
about how excited he is
for concepts in general and
concept syntax in particular
because it will make it easier
to teach concepts in general.
Now this is not gonna be a
talk about concepts in general,
this is gonna be a talk about concepts
in like, really painful particulars.
And I'm gonna try to teach you
all about the ins and
outs of the new concepts
syntax and semantics that are
coming in the working draft.
Now the reason that I call this
concepts as She is Spoke, this is a play
on an old guidebook from the 1850s
that was intended to
teach Portuguese speakers
how to speak English.
It was made by a Portuguese speaker
by translating an existing
Portuguese-French guidebook,
using a French to English dictionary.
He didn't actually know
either French or English.
And that's gonna be kind of the position
that we are in today,
because I'm not saying
that I know concepts,
I just want to teach concepts.
And I know Portuguese,
metaphorically speaking.
So here's a very rough outline.
We're gonna talk about a lot of syntax.
We're gonna talk about
requires-expressions
and requires-clauses.
We're gonna start out by talking about
what's wrong with the good
old fashioned enable_if?
Like why do we need this
kind of syntax at all?
We're gonna talk about concept definitions
and various terse syntaxes
that may or may not make it
into the next version of C++ in the 20.
And some miscellaneous stuff and pitfalls
and in the corner there are slide numbers,
so if you have a question
or comment about a slide,
you can jot down the slide number
and then we can come
back to it if we need to.
And there are microphones here and here
and if you have questions or anything,
if you're at the edges you
can get to the mics easier,
if you're in the middle move to the edge.
Or just shout it out and I can repeat it.
Right.
So if you saw Michael
Price's talk the other day
he also covered basically
everything on this slide
which is awesome,
because I'm gonna cover
the exact same stuff.
What is the state of concepts?
Well concepts can mean
several different things.
First of all there's the
very old C++0x concepts
which were in what became
C++11 and got taken out,
mainly because they
were kind of complicated
and no one understood them.
And the situation has not really changed,
because now there's a concepts TS
and not all of that made it
into the C++20 working draft.
And then there's the working draft itself,
which is sort of a slim
down version of the TS.
The concepts TS syntax
is supported more or less by GCC.
That was the reference implementation
for the concepts technical specification
and you can compile it
using the fConcepts flag.
So if you have GCC trunk
or GCC back to, probably
GCC seven or eight,
this should work.
The only change if you want to use
the working draft syntax is that
GCC requires that you say concept bool
where the standard now says
all you need is concept.
Concepts are always Boolean.
So you can fix that with
a little pound define.
Pound define concept to concept bool,
you're good to go.
The semantics are slightly different
and I'll try to cover that in the slides.
There's also a fork of Clang.
It has not yet been merged.
It's in the process of
being merged to Clang trunk
and should soon be in there
under the regular old std=c++2a flag
and you can play with this on Godbolt.
If you look up in the corner,
anyone who's playing along at home,
goldbolt.org with that
little short link there,
I've set that up with the,
everyone here knows Godbolt, yeah?
Yeah the compiler explorer, awesome.
I've set that up with
Windows side by side,
one with this command line with GCC,
one with the fork of Clang
so that you can type stuff in
and compile it with both and compare them.
MSVC 2018 isn't really in the game.
It basically treats concepts
syntax as white space.
So we're not really gonna
talk about that anymore.
Godbolt.org
Here I made this little
diagram showing you
who supports what.
This is a very silly diagram.
It doesn't really explain
anything about concepts at all,
so ignore this diagram.
Let's talk about what is
a concept, first of all,
and again, this is not gonna teach you
to think in terms of concepts,
mainly because you should go
watch my other talks on templates.
I have a talk from two
years ago on templates,
I have a talk from last
year on A Soupcon of SFINAE
which teaches you how to do SFINAE,
that kind of thing,
and all of that has a sort of substrate
of understanding that
sometimes we want to constrain
a function template
and it only works with
certain kinds of things.
So for example in this case
I've got a stringify function template
and it takes a T, but what
kind of T does it take?
Well, we can try to describe
what kind of T it takes,
we can say, well T needs
to have this operator,
left shift,
and it needs to,
it has a bunch of other constraints too,
not just have to have left shift operator,
it also has to have the correct
type on the left hand side
and the right hand side,
and it has to probably return
in a reference to ostream,
so it's not just that it
has that one operator,
it has to behave in a certain way,
and of course what we want it to do is
turn the thing into a string.
That's what we call a semantic constraint
and we have no solution for that.
But we can at least detect
thing about the syntax.
Now in this case what we're
detecting about the syntax
is really just that it has one operator
and as Bjarne said in his keynote,
we want to avoid having,
defining concepts that
are really just like,
it has a plus operator,
it has a times operator.
So in this case we probably
don't need concepts.
But last year Vinnie Falco gave a talk,
he's also giving another talk
on Friday, which I recommend.
And he used this example
where he had an http server or client
and when the server received a request,
it wrapped it up in this http request
class template,
and the request has a version
in the method and the
target and some fields
and it also has a body,
and he wanted to represent that body as,
sometimes it's a vector of char,
sometimes it's a string,
sometimes it's stored in a
file, that kind of thing.
And so it's templated
depending on what you want
body value type to be.
If body value type is a
string, this will be a string.
And if it is a string,
then body read has to
know how to read a string
and body write has to know
how to write a string.
I hope this is making sense,
and if it's not making
sense, go see Vinnie's talk.
But in this case we have a
whole family of constraints,
requirements that all play together,
so there are many requirements.
Body value type has to exist.
Body read has to exist
and it has to accept these parameters
and it has to do the right thing.
Body write has to exist
and accept these parameters
and do the right thing.
And we might want to
have a notional concept
that tells us whether a class
is a valid class body or not.
Does it have a value
type, a read, and a write,
and can you use them in the proper way.
And let's just say that
we want to make sure
it has all of those
together as a whole package.
We don't want people implementing
just 1/2 of our concept,
we want the whole thing.
So it's useful to have a way
to bundle those all together.
In Vinnie's talk he showed how
to define a concept in C++17
or really going back to 03,
this kind of thing would work.
03 would be hard, 11 this would work.
And this is also covered
in my SFINAE talk.
We have a struct body, a struct is body,
this is a type trait, initially false.
And under these constraints,
the partial specialization
that will make it true.
And then we can static_assert things
such as if we had a class file body
that provided all of these methods,
then it would be is_body,
string_body, same deal
and int of course is not a body
because it doesn't have a value type,
it doesn't have a read or a write.
New school, finally, by the
way if you came in late,
please move up, you're gonna
wanna correct me on things.
Here's how we do it in the new
C++2a working draft syntax.
We have very, very similar,
more verbose in some sense.
It's almost the same number of
lines, it's one line shorter,
but it has a lot fewer angle brackets.
We say concept body requires
three constraints expressed
just as semicolons separated,
terminated things.
It has to have a type,
an A and B value type.
We have to be able to say body read
with these two arguments,
body write with these two arguments,
and in fact there's a typo on the slide,
because that I-S on B write should be O-S.
We can write it to an output stream
and then we can static_assert.
File_body is body that string_body is body
and that int is not a body.
When we go to use it
to assert inside the body of http request
that something is a body,
we static_assert is body value.
You've seen that, we can use enable_if.
We have to enable_if both things.
In the new school,
the static_assert looks almost identical,
except we don't have to say
colon, colon, value anymore.
The concept itself becomes
a Boolean expression.
And we can make two
overloads do something,
one of which is constrained
and the other of which is unconstrained
and therefore a less good match,
and we're gonna talk all
about that in a minute.
I just wanted to put these up to show you
the syntax that we're shooting for.
This is the kind of thing
that if you're coming here
having never seen concepts before,
hopefully this gives you an
idea of where we're going.
Now there's something else
about why the old way is bad.
I said I was gonna tell you why
people don't like enable_if.
Here we have a do_something
function template
and it's templated on body,
but we only want do_something
to exist and compile
if the template parameter
is in fact an acceptable body.
This is the way we'd do
it in the old school,
we would put an extra
template parameter on there
that equals enable_if T
of the condition that
we're trying to test.
And the problem with this
is that we're basically
abusing template parameters.
We have a template type parameter
that's not being used to parametrize.
And a caller who knows
by looking at the code
that we're doing this, or by accident,
can accidentally hijack our implementation
if they say do_something,
that's not gonna compile
because int is not a body.
But if they put some other
type after the comma,
then they've given that type,
it doesn't need to be deduced,
it doesn't need to be defaulted anymore,
and the compiler will happily compile that
and probably you'll get some sort of
cryptic error from the
body when you do that.
But in this case you won't,
it'll just print yes.
So the new way,
using a requires clause,
the requires clause is
not a template parameter.
It doesn't add a template
parameter at all.
It just adds the constraint.
So now the user can't
say do_something
because int is not a body
and they can't say do something
of int, char star star
because the function template do_something
only takes one parameter,
one template parameter.
The requires clause is
not a template parameter.
It serves the same
purpose as the enable_if
in current C++
but it does it in a way
that can't be hijacked.
Now this brings me to
an interesting point,
near and dear to my heart
and probably not near
and dear to your heart,
and also probably subject to change.
The material I'm presenting here
may have a very short shelf life
because I'm talking about stuff
that's not gonna get shipped for two years
and may change in the mean time.
So at the moment, in both GCC and Clang,
requires clauses don't seem to interact
with name mangling at all.
What I mean by that is,
when we have the old school
foo and it has class T
and class defaulted to enable_if T,
that whole thing, that whole
default parameter right here,
this whole default parameter
is going to get evaluated out
and it's gonna come out to void.
So we have one class parameter T
and one class parameter,
it's gonna come out to void
in all meaningful cases.
And we see that show up
in the name mangling.
If we go in with a debugger,
we look at the assembly,
we see the template parameters here.
Here's int, int is i, short is s,
and they both have the
second parameter void.
So it shows up.
However, if we get rid of
that template parameter
and we use our requires clause instead,
then it doesn't show up,
there's no extra parameter here.
This is really a template
of one-template parameter,
that just happens to be constrained
at the source code level.
The compiler knows about the constraint,
the linker does not.
Similarly if we have one of these cases
where we have two foos
that are mutually exclusive
in the old way we would do it like this.
We would have an int
that was enabled only if
is_integral and default to zero,
and then we'd have another one
enabled in the opposite case.
These are two different function templates
with essentially the same mangling.
But it works out because
they're mutually exclusive
and we see that literal integer zero
showing up in our name mangling.
If we do the same thing with
mutually exclusive constraints
on bar using the requires syntax,
we don't see that extra
template parameter showing up.
That's because duplicate
function templates are okay,
function templates are not
the thing that shows up
in the assembly.
What shows up are the templated functions
created from those function templates.
We're 15 minutes in,
so I'm gonna move on a little bit faster
and talk about different
story with class templates.
With class templates
and variable templates
and alias templates cannot be overloaded.
So in this case, if I try
to make a class template
that's unconstrained and I
make one that is constrained,
I'm gonna get an error
because it's gonna say
these two class widgets
look like a re-declaration
of class widget.
You can't overload a class.
You can overload a function,
you can't overload a class.
So you've got your two class templates
with the exact same name,
and I can't deal with
this, you get an error.
However, what you can do
is you can make a partial specialization
by putting the angle brackets.
Partial specializations
always have angle brackets
with something in them.
And once you do that,
you can constrain your
partal specialization
and this works fine.
So we have one class template
with a partial specialization
that's constrained.
And this brings me to an old-school trap
that I covered in a
template normal programming
two years ago and Walter
Brown just talked about
in his talk on Tuesday, or
sorry, Monday afternoon.
How many people have
seen this trap before?
Fair number of hands.
Nice.
So we have two function templates here.
They're all named baz
so it's gonna be hard to refer to them,
but we have A and B.
A and B are both function templates.
and the thing in the middle
is an explicit specialization
of one of these templates.
But which template,
it is a explicit specialization of
depends on where it
appears in the source file.
As it appears between them
here we haven't seen B yet
so the only thing it could
possibly specialize is A,
so it does specialize A.
At this point, if we call baz
and we do overload resolution,
with an int star,
overload resolution
considers two candidates.
It considers the template function
that could be instantiated
from function template A
and it considers the one
that could be instantiated
from function template B.
Now B is the better match.
We're not looking at the partial,
or full specializations yet,
we're just looking at
the templates A and B.
And B is a better match
because it only takes pointers
and we've got a pointer, the
other one is less specialized.
So we're gonna pick template B
and then we're gonna start
looking for specializations
that we could use and
we're not gonna find any,
and we're gonna use the primary template.
And we're not gonna use the specialization
that specialized template
A and so we're gonna end up
with unintuitive behavior in this case.
Whereas if we took that
middle specialization
and we moved it down below B,
then it would get picked
up and we would use it.
So this would be interesting,
surprising behavior,
but well defined.
Now with requires,
because it doesn't
contribute to name mangling,
the stakes are a little bit higher.
I can say here I have A,
which is an unconstrained
function template
just like before, and then I have B.
B now it doesn't have any
more template parameters,
or sorry, doesn't have any
different template parameters.
It takes T and it takes T.
It's exactly as specialized
as A in the old-school sense,
but it has this extra constraint
on it, the requires clause.
So it's gonna be a better match.
So the compiler is gonna use template B
to instantiate a new function,
a function stamped out
from that function template
and it's gonna give it a name.
And its name is gonna
be the same as this one.
This one's gonna get
code gen no matter what,
because we told it,
I want a function that
looks just like this.
We're gonna get code gen for
this under this exact name
and then we're gonna get code gen
for the function instantiated
from this function template
under this exact name.
We're gonna have two functions
with the exact same name
show up in our assembly,
and the link is gonna either
complain or pick one of them
and it's gonna be undefined behavior.
It's gonna be very bad.
How do we fix this?
Well, that red asterisk is indicating
that maybe they're gonna fix it
by just making requires clauses
contribute to name mangling.
That will give you much
longer identifiers of course,
like super long identifiers,
but it will save our undefined
behavior in this case.
I don't know what I want to happen here.
So I was talking about
better match criteria.
Let's talk more about that.
So there are two distinct
better match criteria
that we could have when we're
doing overload resolution.
When we call foo on an int star,
here we have three function templates,
where we've gotten off
of that weird pitfall
and we're talking just about
I have a bunch of templates.
I have an unconstrained one.
I have one that's constrains
but requires clause,
and then I have one
that's more specialized,
it only takes pointers
in the old-school way.
This one also, B takes pointers
only in the new-school way,
C takes pointers in the old-school way.
So C is a better match than a obviously,
because it takes pointers.
Now C is also a better match than B,
because it also only takes
pointers and B takes anything.
The old-school rule has higher priority
and B is a better match than A
because they both take T
but B has a requires clause
and A doesn't.
So the old-school rule
has higher priority.
We look first at the parameter list
and try to figure out which of them
would be highest specialized in C++17.
And only if we still have a tie
do we go and we look
at the requires clause.
This may lead to surprising behavior
if you mix concepts with
old-school enable_if.
If you're thinking about doing that,
in the same overload
set, don't think that.
And more especially don't do that.
If you do it even without
thinking about it,
that's probably worse.
So what does it mean that
B is a better match than A
because there's more constraint?
I mean, in this case
it's very obvious right?
We have a requires clause here
and then we don't ever
requires clause on A
so obviously that's more constraint.
But what about something like C?
Here I'm saying we'll C,
works on any type whose size is four.
Well, there are some
types that are size four
and integral some that are
size four and not integral,
some that are integral
and don't have size four.
So intuitively, neither B or C
is more constrained than the other,
and if I were to pass
let's say an int to foo,
I would expect the compiler to tell me
that it was ambiguous,
it didn't know which of B or C to use,
because neither of them
was more constraints.
That just sort of intuitively makes sense.
But what if I had something like D?
Here I'm saying that D works
in anything whose size isn't zero.
Now four isn't zero.
So is C more constrained than D?
What do people think?
Is C more constrained than D?
Is C not, are they incommensurable?
Surprising number of people
understood the word incommensurable.
Logically D is less constrained than C,
but we don't seriously except
the compiler to know that,
right, 'cause we are
smarter than the compiler,
we know what incommensurable means.
And so intuitively it makes sense.
B, C and D are all
equally much constrained,
or incommensurate.
We don't expect the compiler
to be able to resolve the ambiguity.
Also notice that in D's
case there is literally no T
whose size is not zero,
except possibly void.
So we really only have two levels,
like can the compiler ever figure out
that one constraint is more constraining
than another constraint?
Well, what do we expect to happen here?
So I took foo of an int and
that is integral and is signed.
What do we expect to happen
here, is the compiler going to,
who thinks the compiler
is going to pick F?
Who thinks the compiler is gonna pick G?
Who thinks there's an ambiguity?
It was a trick question.
GCC will actually pick G
because it thinks it's more constrained.
The working draft version of concepts
as implemented by Clang
says no, it's ambiguous.
Okay, concepts alert.
What happens if we mix
a concept into the mix?
And now we do the same thing,
but instead of saying is integral
V in our requires clause,
I'm going use the concept,
which of course is defined
as just as integral V.
What do we expect to happen here?
Will the compiler pick J?
Will the compiler pick K?
Will the compiler say
there's an ambiguity?
The compiler will in fact pick K.
This is real interesting.
Here's how this works.
So, requires constraints
are logical formulas.
When I put our requires clause
onto a function declaration
or a template class declaration,
its grammar has to match
the grammar of a logical
expression, a boolean expression,
as developed by these two guys,
Augustus De Morgan and
George Boole respectively.
And so it's composed of
logical-or-expressions,
logical-and-expressions,
and you you put them
together in a certain way,
and then you can talk about
the Platonic mathematical
normal form of an expression.
So if I have a function template
with this particular requires clause,
that says requires AT, or B of T,
the compiler is actually
required, in the working draft,
to lift this up into the Platonic realm
to say, I recognize this
as a logical-or-expression
because it uses the logical-or-operator.
That's magic at this level.
It's gonna lift them up and
put them into these clouds,
this low Venn diagram of clouds,
and it's gonna say uh, A of T or B of T.
There's a mathematical construct,
normal form of a clause
does not mean that you write
it in a particular way.
You don't write it at all,
you just think about it.
Here again, if I have A
of T or B of T and C of T,
I lift those up into the clouds,
and I think about them this way.
And this means that we can
talk about requires clauses
or requirements whose
normal forms are the same.
Not just equivalent,
but the same because they're
in this Platonic realm.
That means that, for example,
logical operations are commutative.
These two requires clauses
doesn't matter which order
I put the operands in,
they have the same normal form
because we lift them up into clouds
and then they sort of swirl around.
So these are actually the
same constraint in some sense,
but not in some other senses.
These are not re-declarations
of the same template,
or if they are it's undefined behavior,
but they have the requires clauses
with the same normal form.
Non-atomic concepts.
For example, if I have a concept
where I say concept equals
and then an expression that
is itself a logical formula,
well, we think about that
as just a little cloudy
Venn diagram of its own.
We put that into normal form,
and then we can slot that
in to way requires clause,
the normal form of a requires clause.
And we can see that, for example,
these two requires clauses
have exactly the same normal form.
Even though they seem at the syntax level
to involve different
concepts, one has concept AB
and the other has B and then A,
they don't share any names
at the source code level.
But we go look at the definition of AB,
we find out that it's really
just the conjunction of A and B
and we find out they have
the same normal form.
Have any questions about this so far?
Feel like there should
be a lot of questions.
Yeah.
(audience member speaks off mic)
Is the normal form for these expressions,
defined anywhere as a compiler defined,
in the sense of what rules can
they use to manipulate these?
(attendee speaks off the mic)
You could apply De Morgan's law.
We're gonna cover that in eight slides.
Oh, one more.
- [Woman] (mumbles) Repeat the question.
- Yes (laughs).
The question was, what are
the exact rules for this
and we'll cover that a little
bit more in a few slides.
(attendee speaks off the mic)
Yes, when I say incommensurate,
I mean unordered
that A is not less than
B, or A does not subsume B
and B does not subsume A,
yeah it becomes an ambiguity.
All right.
So in the working draft,
although as I said not in GCC,
we always bottom out at expressions.
So in this case, if we take
the requires clause on foo,
that it requires the T be both integral,
or sorry, either integral or
floating, it can't be both,
but either or, and then we look at
what the definition of those concepts are
and we think about their normal form,
and we see that they depend on a concept
and we look at its normal form.
We sort of break it down piecewise right,
we start with integral, and
we say what is integral?
What is that cloud on
the right handside bottom
that's integral?
Well, that's the conjunction
of scaler and is integral of A
that expression,
and then we break down
floating the same way.
And then we break down
scaler itself in that way,
and so we end up with this
as the normal form of this expression.
We've bottomed out now
because we've hit things
that are not concepts.
They are just in expressions
and these expressions are identified
not by their textual form,
because that could have
macros and whitespace,
and all this other stuff
we don't want to deal with,
they're not identified
by their textual form,
they're identified by
their source position.
Basically, line and column and file.
So if I rewrote my example to
get rid of the scalar concept
and just inlined that
and said is scalar VT
and is integral V and is scalar VT,
now I have two copies of
the expression is scalar VT
in my program at different places.
And they no longer count
as the same, right?
Now GCC actually has some weird heuristic
that makes this work, because
the concepts TS, I think,
has some weird heuristic
that makes this work.
But Clang implements the
working draft which says,
no, if you wrote the scalar V twice,
then that's two separate things.
If you want it to be the same thing,
you need to make one
thing, namely a concept.
The concepts of the things
that we're gonna break down.
Once we get down to these expressions,
the red expression and
the green expression,
are completely different.
Now getting to your question
about De Morgan's rules,
let's say I had a really
complicated expression
where I've got an or and then inside that,
I've got nested an and
and inside that I've got nested an or.
How if I've been drawing
all these Venn diagrams
saying that we just have or
and then it has a bunch of ands
and those have a bunch of clouds.
Can't I have an or inside and.
Well it turns out that if you write this,
we will actually August
De Morgan will come along
and he'll laser zap it,
and it will turn into that.
Just these two requires clauses
have exactly the same normal form.
So that's nice.
But of course that's not
really De Morgan's laws, is it?
That's just distribution.
The Morgan's laws are about negation.
What about negation?
If we have these two,
this is exactly De Morgan's rule right?
What do we expect to happen in this case?
Yeah, becomes long lasers zap.
Who thinks that these are gonna end up
with the same normal form?
Who thinks these are gonna end up
with a different normal form?
Hmm.
The minority position has it in this case,
because C++ comes along and
says no reject your laser eyes.
These are completely different.
There is no special case in the grammar
for the not operator,
there's also no special case
for the literals true and false.
So these requires clauses
are functionally equivalent
in the sense that any T that you put in
is gonna satisfy either both
of them or neither them,
there's no possible T
that can distinguish them.
But they are incommensurable,
neither of them subsumes the other.
There's no possible way that
these won't be ambiguous.
So having completely confused you
about the subject requires clauses,
now I'm gonna completely
confuse you about,
oh wait, there's one more slide.
A situation I think is sort
of conceptually similar
to covariance and contravariance.
The way that you can have
covariant return types in C++,
but you can't have
contravariant parameter types.
I don't know, think about that.
But now I'm gonna confuse you all
about the concept definition syntax.
Here's the concept definition syntax.
We say we have a template,
template parameter list,
and then we name our concept,
and then we have this
constraint expression.
And this constraint expression
is just the same constraint
expression we saw
attached to templates,
the function templates, as
we were just looking at.
And that's how we're able to take it
and explode it out when
we're doing the substitution,
creating a normal form.
We're taking this and
substituting into it,
substituting it into a constraint
expression somewhere else
or a piece of a constraint
expression somewhere else.
So anything that we can put
into this constraint expression
we can put into a requires
expression and vice versa,
or sorry, our requires
clause and vice versa.
We can put in a disjunction.
We can put in a conjunction,
a primary expression of type
bool, such as true or false,
or a boolean expression, or a type trait,
or something called a requires expression.
Now a requires expression,
thank you Unicode,
is not the same as a requires clause.
This is gonna be different.
This is gonna be very, very different.
This is not a subtle thing.
Requires clause is a boolean expression.
A requires expression is
something more complicated.
This is kind of similar to the
situation that we have today
in C++11, with noexcept expressions,
versus noexcept clauses.
A noexcept clause is
part of a declaration.
You put it on a declaration
possibly of the template declaration,
and it says this is noexcept when
and then some expression
tells you when it's noexcept.
A noexcept expression
is a constant expression of type bool.
It asks the question, is this
expression they'll accept,
and allows you to detect
when that is the case.
And then you can put it
into a context for bool.
Now, sometimes we're gonna combine these.
We're gonna say noexcept
noexcept and then the expression
that means this is noexcept
when this other thing is noexcept, right,
or we can separate the two
and we can make a trait
that says, is this thing noexcept,
and then we can say XYZZY is
noexcept if T is noexcept,
T is noexceptfooable.
Requires clauses are
gonna work the same way.
Requires clause as part of a declaration.
And it says it declares
that this participates
in overload resolution when
this boolean expression is true.
And that was the thing
we were just looking at.
A requires expression is a
constant expression of type bool,
and it asks is this set
of requirements satisfied
and it looks very different, right?
It takes a parameter list
and a list of requirements
that were gonna talk
all about in a minute.
And again, sometimes a requires clause
contains a requires expression,
say, template void XYZZY,
requires requires and in this means,
when these requirements are satisfied,
then I will participate
an overload resolution.
Otherwise I won't.
But much more commonly
we would separate the two
when we would put the requires
expression into a concept
and we would give it a
nice name like fooable,
and then we would put the
concept into the requires clause.
Right.
So you'll hear people making
fun of requires requires.
It probably deserves it,
but this is the
philosophical reason for it.
So let's talk about what goes
in the requirements sequence.
We can have in here, any
of these kinds of things.
We can say I require
that this type expression
is in fact a type, that it exists,
that this member type
def exists, or whatever.
We can say that I require
that this expression is well-formed,
this expression is noexcept,
that this expression has
this particular type,
or matches this particular concept,
or I can have a sort of sub requirement
of another constraint expression there.
So, for example, I require
that TS member type def,
a value type.
I require that a const T plus
an integer is well-formed.
I require that when I take an integer
and I plus equals it to const
T, that has to be noexcept.
I require that that is in
fact convertible to T ref.
That is highly unlikely to be satisfied
because you can't plus equals a const T
in the first place probably,
or I require that when
I subtract two things,
I get something that matches integral.
Is that a question back there?
No.
Whoops.
And you can combine some of these.
Some of these have convenient
syntaxes that get shorter.
Right.
Right, right.
You can chain them together.
You can say I require that
this first expression,
CT plus equals I is
well-formed, is noexcept
and is convertible to T ref.
I require that when I
subtract two const Ts,
that is well-formed,
is noexcept that matches
the integral concept.
Now GCC and the concepts TS
also support using auto in this place.
The working draft does not.
You can simulate it by
saying that I require
that the decla type of this is not void.
I require that this is a
point or that kind of thing.
I suspect that this will
come to the working draft
specifically because ranges
TS uses it all over the place,
and I assume they want
that to be standard C++.
So probably they'll do
that just by adding auto.
Now that you've seen the
syntax, really quickly,
and you're certainly experts by now
on everything about concepts,
I wanted to show you some traps.
These are just fun little things
and we have 20 minutes left
and so much more to cover.
But let's pause and take a breather
and think about why does this fail?
I'm saying that I want T to be negatable,
it means it has a minus operator
and char certainly has a minus operator.
So why does this fail?
First of all, how many people
think they know the answer
and then I'll ask you why?
All right, why is this?
The braces are missing.
Yes.
This works great.
We forgot the curly braces
and so that arrow is not the
arrow, it's the arrow operator.
We're asking whether T arrow
some member, capital T,
is negatable or not.
If we put the braces, then it's great.
- [Man] Is there an error message?
- Oh, there is no error message.
This is real code.
If I static assert negateable
char it fails, it's false,
and if I static assert
that S star is negateable,
it says yeah, it is.
Yeah, there is no error message
because this is valid code.
Right, forgot the curly braces.
We fix it by adding curly braces.
How about this one?
I say intSize requires
that the size of T is four
and then I ask is char intSized?
And it says, yeah, yeah, char is intSized.
What went wrong here?
You're testing well-formedness.
Yes.
Size of T equal four is in
fact a well-formed expression.
It has the value false, but
it is completely well-formed.
What we meant to say was
requires size of T equals four,
or, of course we could have eliminated
the requires altogether.
And what about this case?
Gasper doesn't get to answer this one.
Oh wait, it's on the slide.
(audience laughing)
We forgot the requires
keyword, that was the answer.
Did you all get that?
So we can requires again
that plus plus T is noexcept
or that plus plus T is noexcept
using the postfix syntax.
The reason that we have the two syntaxes,
they requires noexcept,
but then also the postfix noexcept syntax
is because the latter is more convenient,
if we're trying to mash
it all onto one line.
So we can say plus plus T
noexcept is convertible to T ref
and it all fits on one line.
Oh, is there a question?
There's a question.
Come closer.
- [Man] Basically I was just wondering,
so when you have a parameter list,
the purpose of that is to
actually drive everything in.
You don't have to worry
about (speaks off mic).
- Yeah.
The question is about the parameter lists.
So what's the purpose
of the parameter list?
Is it just a shorthand for declval?
Yes and no.
It is definitely a shorthand.
It is not a shorthand for declval,
because these parameters
actually behave the same way
that they would in a
function parameter list.
For example, if one of them was T ref ref,
I said T ref ref T and then I used T,
sorry, T ref ref R and then
I use R down in the clause,
R down there is not an R
value reference, right,
because it's got a name,
so it's an L value.
So if you want it to be an R value,
you have to explicitly
still move it at that point,
or forward it or something.
And then the other purpose
is that anything I put
up in the parameter list,
in this case up here, I have a
parameter of type value type,
so that will actually be
a constraint at that point
that that type has to exist.
I don't have to then redundantly
say type name T value type
inside the parameter
list contributes to the.
Yeah.
Don't I need a type name?
Not anymore.
I wish I had still did because
that was super easy to teach
but yeah, it can figure
out at this point in 20
that anytime you're in a
parameter list context,
you don't need type name.
That was a change in 20,
that's already in the working draft.
All right.
So there were some traps.
Let's talk about terse syntax,
you've all heard about terse syntax.
So if there is one terse R syntax
already in the working draft.
I'm not a big fan of it,
I prefer to see the
explicit requires clauses
just because I feel like
it's much more explicit
and more general-purpose.
You don't have to worry about
weird corner cases as much.
But if you like it, here's how it works.
We can, in the place
where we'd normally say template class T,
or template type name
T if you enjoy typing
and we can take that class
or type name keyword out
and put a concept in.
So when we say template integral T
what that means is, what
it says on the slide,
template class T requires
that T is integral.
Now, this is very different
if I put auto in there,
of course it would mean
it takes a value like 42.
If I say integral T it means
it takes a type like int,
whereas the type has to be integral.
And that's one reason
I don't really like it.
Another thing you have to watch out for,
if you start really getting into this,
like oh yeah, I'm gonna
make everything concise,
is you can wind up in
situations like these.
Here I have a template foo that takes an R
and it says R has to be a reference.
I accept anything that's a reference.
Now what goes wrong here?
Well, it's taking it by value.
It's doing type deduction on
some thing or some class R,
and then it's saying, but
only if R is a reference,
but I've already done deduction on it.
It's never possibly gonna be a reference
because I'm taking it by value.
So this is never satisfied
just like Duke Ellington.
Also, if I take a function type,
and I say I accept anything
that's a function type,
of course that's not gonna work.
Because I'd never take a function type,
I take a function pointer type.
Also watch out for perfect forwarding.
If I say I have a concept integral,
that it satisfies only integral types
and then I try to perfect forward one in,
this will work great for our values.
If T is deduced as int, int is integral,
but if I try to pass into ie to it,
well, that's an L value.
So if I pass an L value to a
perfect forwarding function,
what is T deduced as?
All together now.
Int ref.
One more time, what is T deduced as?
Int ref.
So in this case, our int
ref, is int ref integral?
No, it's not, all right?
So that won't compile.
So, if you're doing this
with perfect forwarding,
you need some other strategy
when you're designing your concepts.
For example,
integral might be a really,
really terrible concept,
almost certainly is.
However, in Bjarne's keynote
he didn't use integral
as a concept, he used
numeric as a concept.
And for all we know, int
ref might be numeric, right,
constant ref could be numeric.
But without knowing those things for sure,
you'd better be careful whenever
you do perfect forwarding,
or in general, play with
references and value categories.
So the ranges TS has a
way of dealing with this,
essentially that they do try
to propagate the value category
all the way through.
So we could say here I'm gonna
perfect forward something
and I require that T be arranged,
but is that gonna work?
Yeah, it's gonna work.
The notion of being a
range is actually satisfied
only by things that I'm
planning the perfect forward.
So in all of these expressions
inside the concept of range,
I know that I'm gonna
use the concept range
with functions that do perfect forwarding.
Therefore, when I'm inside the concept
saying this has to be well-formed
and that has to be well-formed,
that has to be well-formed,
I'm gonna put stead
forward around all of them,
because I know that's how my user
is actually gonna call the thing.
(audience member speaks off mic)
Is it iterated on the concept.
Well, first of all I forgot
to put the curly braces,
so this isn't gonna work.
But yes, iterator would be a concept,
but also there are curly braces missing.
Fell into my own pit.
Yeah, down here.
For multiple parameter concepts.
Yeah, we can talk about
multi-parameter concepts.
The question is about
multi-parameter concepts,
we'll come back to that.
Terse syntax.
So there's an even terser
syntax in the concepts TS.
This is what is generally
known as the terse syntax.
So I've been calling
other things terse syntax,
because I don't even like them,
but this one the committee
didn't like it so much,
that's not in the standard yet.
So here you can actually
if you take where we would
normally put a type name,
and you just put a concept name instead,
you're implicitly saying I accept anything
that matches this concept
and so I am a template.
So this code is actually
valid concepts TS syntax
and if you go back to Godbolt
and you try this in GNU in the
GCC compiler, it will work.
In the Clang fork it won't.
(attendee speaks off mic)
Does this work in lambdas.
The lambda syntax
was sort of the camel's
nose in the tent for this,
the fact you can say auto in lambdas,
and I believe that this
should also work in lambdas.
I'm not sure if GCC supports that part,
but that's the idea.
Oh, does this work in the
working draft in lambdas?
I believe it does not.
I might be wrong about that,
but I don't think I am.
You can put requires clauses on lambdas
in the working draft.
Neither GCC nor Clang
have caught up to that
as far as I know.
So the concepts TS also extends
constrained return types
and variable type deduction,
so you can say foo returns integral.
Now this means something
very, very different.
This doesn't make
through a template right,
because you can't say well,
I return a T please deduce it for me.
What this is saying is if the,
it's just like returning auto right,
deduce my return type, but
then I'm gonna assert to you
that I really expect it to be integral
and if it's not integral,
I want you to give me an
error at compile time.
There's none of this does not participate
in overload resolution.
It definitely participates,
we got all the way to returning the thing
and we find out it
doesn't match the concept
and then we have to
basically static assert.
Now, this is exactly analogous
to how it works in C++11
and later with auto.
When I say auto star foo, and
then I try to return something
that is not in fact convertible
to auto star, like int,
if I return an int star, it works great.
It deduces the auto is
end, and if I return int,
it says that's not an auto star
and it gives me an error, static assert.
So I'm actually kind of okay with this.
This is analogous to
how it works with auto.
I think I can deal with this.
It's not how it works with
any of the rest of concepts,
but it works with auto.
You might ask, can I make a
concept that's not a template?
The answer is no.
Can you make a concept that's
a really weird template,
like a concept on the
takes a class template?
It takes a template template parameter.
Can I make a concept that
takes an int parameter?
Yes, you can do both of these things.
Are they useful?
No.
Concepts can of course be
nested inside namespaces.
The range is TS though.
There's a namespace ranges
and it puts a bunch of concepts in there.
This is a good idea if you're
making your own concepts,
put them inside namespaces.
Can you make concepts into static members
of classes or structs?
No, not yet I wish you could.
Can you pass them as template parameters?
No, not yet, I wish you could.
Multi-parameter concepts,
getting to the question from earlier,
we can have multiple template
parameters on a concept
and, of course, we can mix and match.
This has three classes.
I could have a very attic concept.
I could have a concept that
takes a class an int in
and a class template and
then three more classes.
I can have a concept of auto.
So in this case I have
class A, class B, class C,
and this concept is satisfied
when A is bigger than B is bigger than C,
so this static assert will pass.
Where this gets weird is when I mix this
with the constraint parameter syntax
that I was talking about
earlier, the terser syntax,
where this is like template class T,
but it requires that D is
bigger than int two and int one.
So it reads correctly in English
when you read it out loud,
D is bigger than int two and int one.
But notice where D goes in
that list of parameters,
it goes all the way at the front.
So we're writing it out of
order so that when we read it,
it comes out right.
And I think that was your
question about currying order.
Yes.
So, even though I say not to use
constraint parameter syntax
because I find it weird
and likely to give you subtle bugs
as opposed to really
honking expert only bugs,
its existence suggests
some style guidelines
for the naming of concepts.
The first template parameter is magic.
If anyone does use
constraint parameter syntax,
that's the parameter that
they're gonna be constraining,
so, for example, source
is convertible to dest.
F is the generator of return type.
And vice versa, dest is
constructible from source
so that the parameter order there matters.
But don't strain yourself,
maybe the concept
just isn't going to be used
with constraint parameter syntax,
so that just makes sense
to put the parameters
in the most natural order for reading
and it requires clause
requires that these three
things are mergeable.
Of course, in that case,
if you're not going to use a
constraint parameter syntax
and you're never gonna use it
in a context requiring subsumption,
the plutonic cloudy swirly normal form
that we were talking about.
If you're never gonna need
to compare its normal form
to something else,
then maybe you don't need
it to be a concept at all
and you could just stick
with the old-school thing.
Constraint parameter syntax
eliminates the visual distinction
between type and non type
parameters, all right?
When I say template auto X,
that's a value parameter like 42.
When I say template
integral X, that's a type,
that's like int.
So to try to make this
easier to reason about,
I strongly recommend
that if you make any
non type concepts at all
which again you really shouldn't,
I can't think of any good use for them,
but if you do that,
put the suffix value on
your non type concepts.
So arithmetic type, arithmetic
value would be a concept
that takes a value.
Right, and we saw this
concept with even value.
Constraint non template functions.
This is something that I got
a little bit excited about
when I thought about the possibility
that I could have two versions
of byte swap, for example,
or two versions of some other function,
and then I could constrain them
to say this is the one I want to use
when size of int is two,
this is the one I want to use
when size of int is four,
this is the one I want to use
on little-endian systems,
this one I want to use
on big-endian systems,
and then I could just rely on the compiler
to compile just one of
them and not the other.
No, this doesn't work, don't do this.
This will only end in tears
because requires means
it doesn't participate
in overload resolution,
it will never be called.
It doesn't mean that the
compiler is not going to try
to compile the function,
it's not if const expert.
If it's not a template,
it's a regular old function
and it's gonna get compiled
and in this case we're gonna get
two different byte swap functions
with exactly the same name
and the linkers not gonna
be happy about that,
even though one of them is never called.
For more information on concepts,
there's a bunch of places,
cpp reference is really
great on all counts,
but it is already up to date
on what's in the working
draft for concepts.
I believe it also has
pages on the concepts TS
if that is interesting to you.
There is a CPP Lang Slack channel,
which I assume everyone knows about
because you're at CPPCon.
Oh, and also speaking
to people on YouTube,
there's a CPP Lang Slack channel.
And also, if you're
watching this on YouTube,
and it is later than about
2019, this is all wrong.
Please turn it off and go away.
Sorry, I didn't tell you that earlier.
- [Man] Should have said
that at the beginning.
- For example, of uses in practice,
the ranges TS is really great.
We should all read it, including myself.
I haven't read it.
It is 200 pages.
And for background information,
there are a couple of standards
proposals or position papers
that cover a lot of the issues,
design issues, related to this.
And I see people lining up for questions,
and we have three minutes left.
So let's do it.
- [Man] You mentioned that you
can't use concept constraints
on non template functions.
What about non template function members
of a template class?
- What about non template
members of a class?
- I believe--
- Of a template class?
If the class itself has
template parameters,
but the function itself does not.
- I believe that what I said still applies
that it means that that
member will never participate
in overload resolution, but
it will still get code gen
and it still must be valid.
It's not an if constexpr.
Well, but because it's a
member of a class template,
it doesn't necessarily get code gens.
You can probably get away with it.
I don't actually know.
- [Man] I mean, I would think
that would be a very useful feature
because you instantiate temp classes
with some members that
just should not be called
with certain types that
you instantiated and.
- Yes, Bjarne's keynote
actually showed an example
of a smart pointer where
they had an operator arrow
that was constrained to only work
when the type T was a class.
He said requires is class T
and what he should have
said it requires is class
remove const T of T, of course,
but this stuff is tricky.
Yeah, I think you're right about that.
- [Man] Except for the terse syntax,
is there really any difference
between a concept declaration
and a const expression bool declaration?
- Is there any difference
between Constexper bool foo
equals something and concept
foo equals something?
Yes, all of the rules about normal form,
whether when I say foo of
T, is that an expression
or is that a reference to this concept
that now I'm gonna go expand that concept?
- [Man] Okay, thank you.
- I'm not suggesting this
seriously, but I'm just wondering
should we introduce
something like requires auto,
where compiler will take every line
which has dependence on template
parameter and just end them
and say if you use that syntax
in your body or the function
and it immediately becomes requires.
It might help with trivial functions.
- Yeah, should we have a requires auto
that means, essentially,
I participate in overload resolution
if my body is well-formed?
Yeah, that could be very nice.
I don't think that's gonna happen,
but that sounds really cool.
I don't see any problems with it
other than that it sounds scary.
- [Man] Well, repeating
function body several times
is also not like best solution?
- The requires syntax
is actually really nice
because the way that we would
have done that before concepts
is by giving it some sort
of spin A constraint,
this returns declval type of my body
and C++11 actually they put
returns declval type auto
into the language,
but it doesn't mean what
you want it to me (laughs).
It doesn't mean I'm gonna deduce.
- [Man] Also, in noexcept I have to repeat
the body several times.
Like noexcept, the body
itself, now requires.
- Yep.
Yes, noexcept auto, I'm
also a big fan of that,
and now we can say requires auto
and as long as people
know declval type auto
doesn't work that way, they will be okay.
Yeah.
- [Man] In your slide about,
you had a negation concept,
so I immediately wondered
if it's possible,
for instance, to have a requirement
that minus minus T equals T,
T being a value--
- To have a constraint or need
that minus minus T actually
does return a reference to T
and not some other object.
- [Man] Yeah, with parents
of course (laughs).
- That is not a requirement at
the level of the type system.
That's a requirement about
what it's gonna do at runtime.
And if I don't have the body
of minus minus T available,
I can't possibly check that.
In general, that's what we
call a semantic constraint
where you have some sort of thing
you want to be true of the behavior,
not just the form, the behavior.
And you can't know the
behavior, unfortunately ever
because halting problem.
And so that's a really hard problem
that nobody has an answer to
and we're gonna standardize
concepts anyway.
- [Man] Okay.
- [Man] A very quick question,
we were just going over some edge cases
with regards to constraints
and overload resolution,
and I'm just wondering should
we assume, or can we assume,
that it's generally the case
that constraints kind
of come into the picture
for deciding when something
is more specialized last,
like they always come in,
all of the old rules
always take precedence
over the new constraints?
Is that generally the case?
Can I use that as a rule of thumb,
is basically what I'm asking?
- Yes, I am not 100% on what it is.
There's a couple more bullets
in the list after that,
but all of the old-school
rules apply first,
things like Wilson takes T
star and that one takes T,
T star is more specialized.
Those all apply first,
and then at the very end,
so that we don't break any
old code at the very, very end
then we start looking at the new stuff.
- [Man] Awesome, thanks.
- [Man] Hi, a quick question.
So about naming style, are
we just going with camel case
instead of tennis course with
concept names in general?
- Yes, please.
Yes, camel case.
- Thanks.
- I think Pascal case or
whatever you want to call it.
I did promise in this talk to
tell you about giraffe case
and what it is and why to use it or not,
and the answer is some people
use it in standards papers,
and I grilled the
committee why giraffe case,
where'd that come from, should we use it,
and both Bjarne and Herb said,
Oh, I don't know, I just saw
it somewhere and why change it?
- Okay, thank you.
- No don't do that.
Stop doing that, use camel case.
- [Man] If I have a concept
that's expensive to evaluate
and I'm trying to get good compile-time
can I still rely on the
short circuiting abandon or,
or is the normal form stuff
going to play with my
intuitions around that?
- Ooh, can you rely on short
circuiting of and and or
and requires expressions?
I believe that you can.
I mean, the only place where
it matters, as you say,
is for
compile time,
and also for if you have something
that might actually require
to instantiate a template
and then that the body of the template
has something ill-formed in it
and that gives you a hard error
and short-circuiting can cause
you to skip that hard error
or hit it depending on the
order in which things happen.
I don't know off the top of my head
if there's like a good answer.
I can't tell you like
yeah, that's gonna happen,
or no it's never gonna happen
then you can count on it,
but it's something to think about.
- [Man] This is a confirmation
of understanding question.
So are concepts anything more
than a bundle of type traits
and requires clauses?
- Are concepts?
- [Man] Should we think of them
as something different from
if we had explicitly written,
like a few type traits, a few requires,
and after our function
parameters, function declaration,
or shall we think of
concepts as something more
than just a bundle they're made of.
- Are concepts more than
just bundles of type traits?
Well, they're these
structured bundles, right,
that a concept, unlike a
regular old expression,
a regular expression
is just an expression.
A concept has structure in
the form of ands and ors
that contribute to subsumption,
which contributes to overload resolution
if you have two things that
could both be satisfied.
But we can tell that this concept
subsumes this other concept,
we're gonna prefer the one
that's more subsomey.
That is essentially the only difference.
Other than that they behave
exactly like type traits.
You could use them exactly as type traits.
Yeah, so, for example, when
I showed this example here,
if I call foo with an int,
well here there's only one,
but if I had a foo that
took something integral
and a foo that took something scalar
that would be unambiguous.
Int would prefer integral
because integral is more
specialized than scalar,
specifically because concept integral
is a logical expression
that includes scalar as a sub expression.
So the compiler can see that structure.
If it were just the type trait
it wouldn't be able to see that structure.
That is the only difference
between a concept and a type trait.
And if you don't need that difference,
then you don't need concepts.
Now, because of that structure
they may still be more efficient
for the compiler possibly.
I would kind of have to
see it to believe it,
but I'm not saying that
type traits are better,
but semantically
subsumption is the one thing
that distinguishes
concepts from type traits.
- [Man] I see.
Thank you very much.
- All right.
I don't see anyone else
with mics, so thank you.
Session's been over
for a while (chuckles).
(audience applause)
