- I gave this talk a while
back, almost two years ago,
in a very small venue, and the reaction to
it was stunned surprise,
after what Bjorn said this
morning in his keynote,
I think this is all accepted
practice at this point,
so I'm not expecting to say
anything too surprising to you,
but I do want to emphasize a
few points about modern C++,
so there has been a
progression in the way we
treat our interfaces,
policy-based design was very
popular in the early 2000s,
and that was a design technique that
basically took well understood
engineering problems,
and identified certain
decision points in a design,
and then we reified those decision points,
I love reification,
it's a term from sociology,
it means take something that's
insubstantial give it a name,
given operations,
so you can reason about
it in a very concrete way,
so when we're doing a
well understood design,
we make a decision, how
do I represent this data,
how do I respond to this event,
and rather than make the decision,
we indicate that there
was a decision to made,
And defer the actual decision
to the user of the components,
so they can produce
customized components in a
very straightforward way,
depending on the context
in which they are used,
and if you remember Andrei
Alexandrescu's Modern C++ Design,
which did a lot of policy-based design,
he focused on design patterns,
which we're basically well
understood engineering techniques,
That we're customized according to the
context of the replication,
so policy-based design
with a good fit for that,
but there was one problem,
it required an expert
user to use it correctly,
so we try to finesse that
by having default design
decisions to be taken by
a less experienced user,
but things are getting more
complex have you noticed,
remember the code you wrote 40 years ago?
So what we'd like to do now
is we tend to move experience
into our code instead of
putting it in the interface,
so we want to simplify the
interfaces but still have
experienced adaptable code,
so we try to do an embedding
of our knowledge into the code,
at least before we have concepts,
and even after concepts,
we used SFINAE based techniques to make
interfaces smarter, or more natural,
so this increased complexity,
how do we deal with complexity in C++,
or any complex linguistic situation,
Convention, and Idiom,
the more complex the
language the more idioms
there are because idioms tend to simplify,
they're like lower-level design patterns,
they tell you how to create
high-level structures,
with low-level components,
so you can raise the level of discourse,
speak design and communicate
at a higher level,
and thereby simplify your designs,
so idiom is very important,
and part of that is actually
creating interfaces that
express your intent very well,
and one of the nice
things about modern C++ is
we have a lot of features
that make this easy to do,
so here is something that is no longer,
a devisive statement,
C++ is now so complex,
that it's become easier to use,
because we've hit a
level of complexity where
we have to use convention,
we have to use idiom,
we have to use techniques
to simplify interfaces,
and as a result it's easy
to program in C++ now,
it's a lot easier than it was in 1998,
to write a correct easy to use interface,
we could have put Scott Myers out of
business if we did this some years ago,
that's a joke guys,
(laughter)
so we'll look at convention, idiom,
embedding your experience in your code,
and "do what I mean interfaces",
and these are things we're
doing all the time now,
but this is how we started out,
we started out by threatening people,
that works in some situations,
but not universally,
you're already a C++
programmer so you must be
fearless from the get go,
so no one is going to
intimidate you with a comment,
he's not even going to read the comment,
which is kind of the problem,
here's a simple function that takes an
array and makes a copy of it,
but it does a mem copy to make the copy,
so of course everybody knows,
you go to somebody on the
street and say would you
mem copy a string, they would say no,
they probably would because they probably
wouldn't be mem copying a string,
but this comment is not
really helping us very much,
because somebody somewhere
is going to do something,
so we decided to become totalitarian,
we've given up direct
threats and we're just
preventing people from
thinking in the wrong way,
so we'll insert a constraint,
and this is something
we could do for decades,
it's much easier and nicer
to do it now in modern C++,
but it's something
achievable a long time ago,
it just became fashionable more recently,
in response to more complexity,
for instance how would this feel,
would somebody actually pass an array of,
non mem copyable types to this?
Well yes, but let's pretend they wouldn't,
so how do we get a bug?
Here's an observation from
many years in the trenches,
most good bugs are team efforts,
it requires cooperation from a number of
people to produce a really good bug,
so one bug here is hoping
the comment is enough to
dissuade people from
sending something that's
not mem copyable,
and so that's fine,
the original user of this
function might pass an
array of structs,
that contain things like
integers and doubles and
arrays of characters,
which are mem copyable,
we can just mem copy
those things and forget about it,
I don't mean to offend anybody,
alright I do,
what happens around mid-May every year,
your code starts to break, right?
Why?
Summer interns.
When I say summer intern that's what's
known as an ideal type in sociology,
it's a role they take on in
certain social situations,
so we're all interns on a bad day,
so sometime around mid-May,
or if you're behaving
like a summer intern,
somebody who doesn't even know about
this code is going to modify that struct,
and replace that array of
characters with a string,
what's going to happen now?
The code is still going to work,
because the string has a
small string optimization,
it stores a string as
an array of characters,
unless it gets long,
when do you encounter a long string?
In the demonstration
that been broadcast to
everyone on the planet,
so you have broken the code,
you just don't know it yet,
so a bug like that is a real team effort,
so by doing this, we're really able to
improve the safety of our code,
we're able to after selecting
this function have a
constraint on it,
this is not a concept, this
is acting after the fact,
and that's fine, a lot of times
that's what we want to do,
so we can make sure
that what we're passing
here is indeed trivially copyable,
what we'd really like to do
is make code experienced,
we want to take all of her knowledge and
experience and embedded in our code,
making ourselves redundant,
never mind,
so what are we doing now,
this is really really simple,
we're saying based on my experience if
something is trivially
copyable, I can do and mem copy,
otherwise if has a
nothrow copy constructor,
I can do this beautiful placement loop,
and you have to love the statement,
I just called the copy constructor for T,
and successfully initialized previously
uninitialized memory,
not worrying about exceptions,
and what's the third case?
Well what if the copy constructor
could throw an exception?
We have to take care of that,
at this point someone
should say will isn't
there a standard library
function for this,
the answer is yes,
who's going to write the
standard library function?
So this code actually adjusts itself,
depending on properties of
the type that is passed to it,
and that's nice,
it works very well provided all of this
code is mutually compatible,
in other words if this complies correctly,
this must also compile correctly,
and that's not always the case,
so,
I can't remember if I
brought the slide or not,
but in modern C++,
we can use a constexpr if instead,
so this is now a compile time constant,
this is a template that was
under two phase translation,
the first phase is parsed,
and non-dependent names are bound,
second phase we complete the translation
knowing what type T is,
the nice thing about constexpr if,
is it will not engage in the
second phase of translation
in a path that's not going to be taken by
this compile time node,
so the nice thing about that
is you can actually have
mutually incompatible
code in the same function,
and still get a correct translation.
Now this is something we could do by
having a function forward,
do a compile time algorithm selection,
but this is much, much easier to write,
and it is actually clearer,
a non-expert, someone who hasn't been
writing C++ for 30 years,
can see what's going on here,
they can kind of guess what's happening,
that's nice,
so we are embedding or judgment in code,
and the more we can do that the better.
Okay so the interfaces
are certainly simplified,
a single function that copies an array,
and the function itself asks
relevant questions of its
type at compile time and chooses the
correct implementation
based on their judgment,
and that's good,
so what is causing your interfaces to be
potentially more complex?
Well, those initiaizer
list things showed up,
I like initializer lists,
but they're,
privileged,
whenever you match a function,
initializer list wants to come in first,
how many people like to read the
standard before going to bed?
Am I the only one?
If you look at the overloading section,
there's a new paragraph
If there's an initializer list call it,
is basically what it says,
so initializer lists can actually,
if you modified your interface to take an
initializer list, all of a sudden existing
code starts behaving differently,
so that's a very frightening thing,
things that we're formally
calling some other construct are
now calling initializer list constructor,
universal references or
forwarding references,
or whatever they're called these days,
are really greedy,
and if you try to overload
with universal references,
things become nuanced if
you're feeling in a good mood,
or disastrous otherwise,
back compatibility, the
more backward we have
the harder it is to be
backward compatible,
so we are writing language extensions
and compilers that have
to deal with code that
is 30 years old,
and that's a rough thing to do,
have you noticed that
not everybody reads the
standard at night before bed,
and sometimes does things that are not
well defined behaviors?
The history of C++ code is littered with
entire projects that depended
on undefined behavior,
and are now breaking
because they shouldn't
have worked in the first place,
or weren't guaranteed to
work in the first place,
and overloading function
templates there are
more and more rules,
there are some really
fine-grained rules for
deciding when one function,
when one overloaded function
is chosen over another,
particularly in the presence
of reference collapsing,
it's illegal to actually talk about
reference collapsing in this state,
so this increased complexity
is not in itself an advantage,
but the presence of the
complexity has actually forced us
to become better programmers
and better designers,
to depend more on
convention to pay even more
attention to our interfaces,
what have we gotten to help us with this?
Well a lot of people may not realize that
design patterns actually was
an architectural discipline,
Christopher Alexander and his
colleagues developed design
patterns in the context of architecture to
build houses and so on,
and they look to traditional
architectures like
New England houses, and Maryland telescope
houses and things like that,
and they discerned that
they we're generated from a
small set of patterns,
and the patterns were
customized depending on
with the context in which the building was
going to be built and so one,
exactly what we do with our architectural
patterns in software design,
and there's one pattern I really like,
some of them are really important,
how do you organize your town so there are
fewer traffic fatalities,
that's a good one,
T-shaped intersections, just remember that
the next time you design a town.
But some of them are very seem minor,
and one of my favorite
is waist high shelf,
this is an actual pattern,
so you come home with your groceries,
you unlock the door with your teeth,
or you leave the door unlocked,
you kicked the door open,
and you're in with your
groceries, what do you do?
- [Man] Throw them on the counter.
- A counter is not waist high.
You want to have a counter
or something like that,
a temporary place to put things
when you come in the door,
what happens when you don't,
can you still achieve
your goal of getting those
groceries where they're supposed to go,
but it's a whole lot harder,
you'll drop everything,
you'll step on your eggs,
so little tiny conveniences
like that are important,
and they are not important
in and of themselves,
it's nice to have that waist high shelf,
but there are other patterns as well,
one of my other favorite
patterns is the zen view pattern,
when you enter a room from
a certain position there
should be a view on something that stills
your mind and makes you calm,
so you come in, you look out the window,
and there's a tree that you like,
or your neighbors window,
whatever you can see,
and together they actually
leveraged off each other,
these patterns,
anybody who's used design
patterns in design know that
patterns leveraged off of each
other to a very great extent,
it's remarkable how much
a small amount of code
that is designed with nested
patterns can accomplish,
it's almost speaking theoretically,
I hope I'm allowed to do that,
it's almost as if a lot
of the complexity is
moved from the code to the ether of the
design space so you
have less code to write,
anyway, the same thing happens here,
waist high shelf combines with Zen view,
to give you a much better experience,
then it probably makes
you more sufficient and
relaxed for the rest of the day,
and the same thing for
a lot of these things,
going from C++ 11 to C++ 14,
C++ 14 was advertised as minor,
improvements there's nothing minor about
return type deduction,
that makes your life so much easier,
just look at corresponding
C++ 11, C++ 14 code
without return type deduction,
but then you throw in C++
17 thing that seems minor,
it doesn't give us anything
that we couldn't do before,
like the constexpr if,
I could have coded around that,
but when you combine
return type deduction with
constexpr if,
you get functions that
can do calculations and
decide what value they
return depending on their arguments,
it's great, it gives you
a ton of flexibility,
so these little things really help,
constexpr if,
especially those require
participation by the
compiler and so on,
so on to our topic,
I will say that a lot of people
pronounce this term SFINAE,
it's pronounced SFINAE,
why, because that's how I pronounce it,
it also sounds more like Latin,
it sounds like you're educated,
if you say SFINAE, it sounds like you're
spitting or something,
substitution failure is not
an error, really simple,
we couldn't have overloaded
function tables without it,
the compiler tries to do
template argument deduction,
but if it fails as it will
on the second F there,
that's okay as long as there
is a successful substitution,
so it's a preprocessing of overload
resolution in this case,
so that's nice,
what can we do with this,
well we'll see,
I just want to point out
that anotherbig change,
seemingly minor,
is where you can apply
the SFINAE technique to
basically take a function
out of contention,
in C++ 03 the return type
and the argument type
were really all you had going,
by the time the function was chosen all
you could do was constraint checking,
you couldn't say take
this out of contention,
so maybe some other function will match.
You couldn't have easily used it up here,
in the template parameter list,
but now from C++ 11 on, we can,
we can have default template parameters.
So what so what's the big deal,
well has anybody ever
looked at an application on
SFINAE on a return type of a function,
and come away gladdened by the syntax,
no, and syntax is important,
semantics is essential,
but how do you get to the semantics,
you have to pass through the syntax,
and template programming in
C++ has always been simple,
but the syntax has always been horrendous,
so simplifying syntax is
extraordinarily important.
Who are you writing your code for,
not for the machine, not for the compiler,
you're writing it for your colleagues,
and as you know, you're smarter
than all of your colleagues,
so you can't expect them
to be able to read your
code without help.
So syntax is a major problem,
so this is an example of the use of
SFINAE which is probably
unsurprising at this point,
And for reasons unknown
I decided not to use a
virtual function and to
pass a shape base class,
I want to have a compile time polymorphism
to the ships,
I don't know what caused me to do this,
bad childhood or something I don't know,
but the thing is I want to make sure that
T is indeed a shape when
I call this function,
so how can I do that,
well I could call the
function and then once
inside the function say is T a shape,
and if not I'll get a compile time error,
I could do a static assertion,
or I can just say maybe there's some other
mundane shape that I could match,
so I'm going to have a constraint here,
I'm going to use the
enable_if to actually ask if
this T is a shape or something
derived from the shape,
if I'm unable to do deduce the type here,
that this will be taken
out of contention and
perhaps some other
function will be matched,
so this is a terrific technique,
and it gives you the
apparent ability to overload
on arbitrary properties of types,
you can effectively overload
on how big types are,
whether types have
certain nested type names,
whether types have no
throw copy operations,
or any combination of those,
it's extraordinarily flexible,
basically anything you
can say about the type,
you can turn into a
constraint on a function,
and also on a class template,
and also on a class
template specialization,
you can use this technique in
a variety of different places,
so the only problem is
if you only want this
function to much for shapes
which are bigger than six bytes,
and have a nested type name called oops,
and smell nicer whatever,
the syntax is going to be terrible,
no one is going to
understand what you're doing,
so usually what we're going
to do is use another one of
these waist high shelves that
has been provided for us,
this template type,
and we're going to have it using,
and will define its shape to be that
whole mess we just saw before,
now we never have to write this again,
now we can write code that even a
Java programmer will understand.
(laughter)
I apologize for the
Java programmer comment,
I had a very bad incident in the mid-90s,
I don't want to talk
about it it's emotional,
but the children did not get Christmas
presents one year because of Java,
true,
they are now populating our prisons,
the children, not the Java programmers,
although,
here's another code smell,
I love the term cold smell,
it's actually a technical term,
so here we have a range initialization,
we have a heap type some
kind of STL-like container,
and we have arranged initialization,
so when we initialize a heap
with a half open interval
defined by two iterators, we
want to use this range limit,
we also have another two
argument constructor here,
but they don't look
anything alike do they,
shouldn't cause a problem.
Oh, you've had a bad life to?
We have a heap of integers,
and we want, for reasons unknown,
want to initialize that heap
of integers with five zeros
which constructor gets called?
Not the one you meant,
because these are two integers,
they have the same type,
this constructor head takes two things of
two different types,
it takes a size T and basically an int,
so this is a better match,
so I'm going to do in a range
initialization with integers,
unfortunately it won't compile,
but you can try situations
where this does compile,
ouch,
unfortunately this is
the sort of thing that
happens with initialism lists,
if you add them after the fact,
all of a sudden constructors
behave differently,
I am predicting death and
destruction from that,
unless you use SFINAE,
so your intention was that constructor be
called only for iterators,
specifically input iterators,
so what to we do?
Well of course this is a solve problem,
we just use the C++ 98 iterator traits,
specialize it with iterator,
get the category,
remind the compiler it's a type name,
and see if it's the same as
the random access iterator tag,
now I used random access
iterator tag here,
because the actual question
that we want to know is,
is it an input iterator,
when is something an input iterator?
What tags does it have?
Input iterator, bidirectional iterator,
or random access iterator,
so the actual constraint
is a little bit wordy,
it's not something you'd
like to stick in the
middle of the class,
because it would be longer
than the rest of the class,
so of course we want
to simplify the syntax,
so the first thing everybody should do,
as soon as you get a C++ 11 compiler,
and there are people who don't have them,
we should have a drive or something,
(laughter)
so we can define category as a
simplification of getting
the iterator category,
and then we can define things
like is exactly random access,
is the category random
access iterator tag,
and continue in this fashion
until you have something like,
is it an input iterator, well it is,
if it's exactly an input iterator,
or is a forward iterator,
so this is the constraint we want,
and this is true thing is
just something I wrote,
because I could tired of writing the
same thing over and over again,
it's not standard,
and a final syntactic cleanup we're
going to use enable with,
but we're going to simplify it,
this is now no longer a
simple true or false street,
this is a exist or
doesn't exist constraint,
which is what we want for an SFINAE check,
so with that in place
we can write code that
says precisely what we mean,
this is what I meant,
I meant only call this constructor if in
is an STL compliant input iterator,
and that is what I wanted to convey,
and now that is what the
interface is saying as well,
let's just do one more example,
and here's something if
Scott Myers were here,
he would just fulminate
looking at this code,
he says 26 times in his new book,
which is a good book,
I can't believe I said that in public,
but it's an excellent book,
that was not a slight against Scott,
it was a slight against my book sales,
he writes books,
that's the problem, nobody knows.
My wife bought a copy.
We are overloading with
a forwarding reference,
I like the term universal
reference which is Scots term,
because in this case that's the problem,
this reference can accept anything at all,
so what we're we thinking,
why didn't we listen to Scott,
well you shouldn't listen
to Scott completely,
you should listen to Scott
for danger points and
then maybe circumvent his
advice in some way or other,
so here our intent was,
I have X that's been specialized with T,
so this is not a universal
reference it's just an R-value T,
and here's an R-value T,
and what we probably meant was,
if it's not a T then we'd like to
handle that operation this way,
makes sense?
That's probably what we intended,
but not everybody has
a copy of Scott's book,
if they did,
they'd say
"Okay, we are going to
call this operation to,
"instead of operation
use a different name,"
but the trouble is,
I'm going to ask you to believe me
that this is going to cause,
I'm sorry to say believe
me in public like that,
it won't happen again,
have I mentioned that
my wife is a politician?
That's okay.
Believe me.
(laughter)
Okay.
Yeah,
the trouble is this greedy
operation will sometimes seize
arguments that we're intended for this,
in particular if I we're to pass an
L-value T that is not constant,
which function is going to match?
It's going to match the
universal reference,
very surprising, you don't
want to surprise programmers,
they are not happy with surprises.
Okay so what were we thinking?
What we we're thinking is we only meant to
call that if the S is
not similar to the T,
Let's define similar,
similar is the same if you decay two types
and if they're the same type,
so decay is a wonderful
thing from type traits,
it basically says what is the
essential nature of this type,
sounds very deep,
it basically strips off references,
and unnecessary qualifiers
like constant volatile,
and turns arrays into
functions and pointers,
and basically strips the
object down to its essentials,
what you would get if you
we're to pass it by value,
and then we'll define not similar to be
if S and T, these two
arguments, are not similar,
in other words if I decayed them,
if there are different type,
and with that in place I can
actually state my intent,
I only want to call this function
if S is not similar to T,
and I stated my intent clearly and now I
have a do what I mean interface,
now this is not necessarily
the ideal solution,
the ideal solution is to call
this operation something else,
but there are cases when you have this
type of overloading that you
would really like to work,
as always syntax is a problem.
What about self identification,
here is one thing I really
like about the new STL,
transparent function objects,
isn't that a great name,
I have no idea where that comes from,
there are certain things
in the standard you
just wonder what they were thinking,
I mean logically sound stuff,
they call function objects transparent,
and since 1998 standard
there's been a section 3.2,
where there are two
random character strings,
one is Studebaker, the
other is vivisectionist,
I've never had a
satisfactory explanation as
to why those two identifiers were chosen,
and it's probably not pretty.
Maybe a summer job somebody had.
So the point is that
some function objects can
identify themselves as transparent,
meaning that they can
pretty much compare or be
operated on anything,
so here's a lens that can
compare anything to anything
provided that it on an
appropriate list in an operation,
and this turns out to be really handy,
we don't have time to
go over all the details,
basically makes all
those tricky things you
been doing in vector over the years,
applied to things like sets and maps and
other containers you
want to be clever with,
but the point is this function object is
advertising that has that capability,
this is an advertising, I am
a transparent function object,
why transparent,
I would call it flexible or
something, but it's transparent.
And the nice thing is that's set,
now we'll alter its interface based on the
property of the comparator
that you give it,
if that comparator is
transparent you get a
whole new lower bound function that lets
you do a lower bound binary search,
not with the key type,
but maybe something that's like a key,
maybe if the key type of your set is the
structure that contains 500 data members,
but you're only sorting
it on the first one,
you can do a lower bound
with that first data member,
providing you can compare
that to the entire set,
so basically here we have a
cooperation between two types,
we have a function object that
volunteering that its transparent,
we have a interface that
is willing to talk with
such an object and augment its interface,
depending on whether that
object is present or not,
I think it's wonderful,
these are, I call them
Distributed Organic Interfaces,
and thankfully that acronym doesn't
spell anything unpleasant,
so again now we have
these interfaces in this
ecosystem and they're
talking to each other,
transparent function object
talking to containers,
containers are asking questions
of the function objects,
and doing what you mean if
they were able to cooperate.
Here's another example,
I do a lot of work with Dan Sachs,
you may know, a C++ expert,
he obsesses about enums a lot,
he does a lot of hardware
level programming,
there are all these enums all
over the place misbehaving,
and he wants to tame them,
so he came up with an idea of an enum
container that behaves
like an STL container,
and you can do things like
iterate through it and so on,
compile time iterations and
all kinds of clever things,
that hardware people like to do,
and how does that work,
obviously not every scoped
enum is not going to
be in the new container,
so it self identifies,
and we can ask a given container
are you an enum container?
And then we have a lot of
ways of dealing with that,
so a given enum can simply by stating I
am an enum container leverage
all of this other capability.
Okay so types are talking to each other,
but of course,
one of the things we do,
our lives are complex,
our code is complex,
is we end up composing
questions about our types,
we don't want to know just
something is transparent,
we want to know if it's transparent,
and very transparent,
in other words we want to
compose things together,
so here we have a predicate,
don't you love very odd templates,
admit it, you're among friends,
you can talk about it now,
so here the predicate,
this template,
parameter is a parameter
pack of templates,
that can take zero or
more type name arguments,
I should have a type name argument...
But that wouldn't fit,
so this is easy to do,
so I Wanna do things like this,
I Wanna compose is class,
is transparent, is big,
I just made that one up,
and that's the meaning of
happiness actually for a type,
and then I can assert that
the given type is happy,
and if it's not, we can
have a compile time error,
we can also generate a similar enable if,
and SFINAE requirement,
and only call functions with happy types.
Most of these slides were
written after midnight.
How do we implement this,
in the interest of time I
won't go into great detail,
but we could use a first rest,
this is how we do recursive
template specialization,
basically we process the first element and
recursively process the rest of the
elements until you run out of elements,
so that's a nice recursive
way of handling things,
that's the way we dealt with type list,
but now we can do things like
have a const extra function,
To do this evaluation for us,
or and this is my favorite one,
you can use variable templates,
with fold expressions,
this is the hot off the press technique,
C++ 17,
variable templates for C++ 14,
fold expressions are C++ 17,
but look how little code I
have to write these days,
if this is not a waist high
shelf, I don't know what is,
this is,
you're coming home, bag breaks,
and everything falls on the floor,
but you still get things done,
this is you still have one
shelf, big enough for one bag,
this is a waist high shelf,
this is going to make your life easier,
and not only that but it's very easy to
work with these variable templates,
so I can make another variable
template, its a composition,
and I say satisfies my needs consists of,
well of T is signed then it's upon,
but is not polymorphic
and is not an array,
then that satisfies my needs,
it's very easy to compose,
and now I can write something
that actually makes sense.
But that's not good enough,
never satisfied right,
I want to be able to write arbitrarily
complex predicates that anyone can read,
all right any C++ programmer can read.
I was quite smitten by type lists,
I don't know you, but
when I saw type lists
I thought they were very very clever,
and lots of fun,
and I spent a lot of time
doing that instead of
making a living,
but I like to do instead is to have trees,
but I don't want to have trees of types,
I want to have trees of templates,
so I want to create template trees,
and it turns out with modern C++,
this is trivially easy to do,
so the idea is I want to
create template trees that
represent a complex constraint,
so I want to be able
to write something as,
pred1, pred2, pred3,
are templates that are used
as predicates over types,
I want to bring them together in a
very reasonable simple way,
I want to create an abstract
syntax tree and evaluate it,
but this has to happen at compile time,
it has to be free, if it's
not free I'm not interested.
So let's just do this quickly,
the code is available on my website,
with accompanying verbiage.
This is a type predicate,
this is the base class for every AST node,
and we're going to do something like this,
here is my and structure,
this is a literal type,
notice the constexpr constructor,
and the constexpr eval,
all the compiler written
members constexpr and trivial,
and what this does is
it simply remembers the
types of T1 and T2,
and then when you call
eval you get a compile time
result that tells you whether
P1 is true and P2 is true,
you can do the same thing for or,
and of course we don't
want to actually write
function calls style location,
so we are going to overload operators,
I'm going to overload the and operator,
people ask me why why
didn't you over load the
and and or the or or,
was anyone thinking that?
I know you were.
Because I like exclusive or,
it's my favorite operator,
and there is no exclusive operator,
so I decided to use the
bitwise operators for this,
it says something about my personality.
Okay so the idea is we are
going to now have a compile time
way to easily construct
a tree of templates,
and the nice thing about
the const extra functions,
they're terrific for competing values that
you can then use in runtime code,
but the nice thing is there are also
good at completing types,
with no guaranteed artifacts
littering your code and
actually occupying space,
there are purely a
compile time phenomenon if
called correctly,
And so I'm using these
functions to compute
types instead of values,
and very often we think
in the opposite way,
here's an unary operator are not,
so the point is we can
create these syntax trees,
and we have ID type as
well which is a predicate,
and we initialize it in this case,
it's a somewhat limited initialization,
takes a single type name argument,
and I'm omitting some examples,
not all interested predicates
and types are unary,
there are interesting
binary predicates as well,
and an array predicates,
and it's very easy to create binders,
where you can actually bind predicates to
produce single arguments
predicates from two
argument predicates and so on,
and you can also take the
effort to translate the
type traits library,
to you have available is isPod,
for instance for standard isPod,
with this in place, we
can do things like this,
I can define my needs,
this makes no sense whatsoever,
but my needs are I need
my type to be classes,
and either pods or not polymorphic,
exclusive or in shape,
I don't even know what that means,
the point is I was able
to write it easily,
and presumably it mean something
because those are my needs,
your needs are different,
your needs are worse,
so we each have needs and
they are hard to satisfy.
Is this describing anybody
else's relationships?
and we want to be able to evaluate them,
obviously I can evaluate my needs by
calling the eval function with some type,
this will produce a compelled time result,
for me which is nice,
but a little syntactic sugar nice,
so I'm going to provide
a constraint function,
that produces a compile time result,
and an SFINAE constraint as well,
so this guarantees a
true or a false result,
very good for static assertions,
this will give me a void or not,
great for SFINAE,
and with that in place I can
actually specify my needs,
I can see if T is constrained to my needs,
and if not, I can complain,
and I can do a complex
SFINAE exclusion of the
function that doesn't
meet both of our needs,
however those needs are defined,
so this is arbitrary complexity,
and it's very straightforward,
again, this is not giving us
any additional capability,
but it's making it likely that a,
we can easily and efficiently write
these complex constraints,
and b, that people can
actually understand our code,
which is always a plus,
the trouble is this doesn't work,
oh well,
because I wasn't careful,
I overloaded ampersand,
but other people are doing this too,
and this could actually interfere
with other people's code,
I might capture by
accident somebody else's
intended use of ampersand,
but I know how to fix that,
because that's not what I meant,
I meant this is to be
used only if everything,
if P1 and P2 were derived from E.
So now here's my constraint,
I've called in SFINAE to
save my SFINAE toolkit,
now this toolkit will not
affect anyone else's code,
presumably unless they also have a
E base class the same name space,
so that's good.
So, in short,
almost in conclusion,
our designs are getting
more and more complex,
but paradoxically,
just having been programming
in C++ for so many years,
I don't think I'm getting any smarter,
at least that's not the consensus
of the people I talk to,
but my code is getting easier to write,
and I'm running much more complex code,
and is often correct really quick,
and one of the reasons is I'm using,
and embedding my knowledge
in the code, such as this,
I'm checking constraints
statically whenever I can,
I am using higher level interfaces,
do what I mean interfaces,
and shamelessly using SFINAE to
eliminate functions I'm not interested in,
I'd lie in wait searching for convention
whenever I can find it,
and if I can find something
conventional even if
it's unpleasant, I will use it,
and as a result my code
is becoming simpler,
we have always in C++, because C++ is a
significantly complex language,
relied on convention.
When I teach beginning C++ students,
I like to give the example of the
"making new friends" idiom,
"making new friends" idiom is
the one to use when you have a
class template with overloaded
binary operators and
you want to get a
user-defined inversion on
the left argument,
it comes up,
how do you do that, how do you fix it,
how do you solve that problem?
It's a complex thing, it involves
argument depended look up,
and friends, and two phase translation,
and messages from
Jupiter, things like that,
but once you know the
"making new friends" idiom,
you say oh, this again,
and it's done,
not only that, but what you
solve it, you can document,
how do you document the use of the
"making new friends" idiom?
Slash, slash, "making new friends" idiom,
and it's done, you just said it's
"making new friends" idiom,
if they don't know what the idiom is,
they can hire me to teach
them a course or something.
So the point is that I
think my thesis is correct,
and am getting very little
argument about this recently.
C++ is getting easier to use,
it's getting easier to teach,
it's more complex,
but the complexity has been
controlled by C++ itself,
it's almost as if it's an emergent
property of the complexity
of the language,
and until we reach that level it was not
quite easy and straightforward to use,
I don't know how you feel,
but I've been very energized ever since,
just before C++ 11 was
improved, there was a
long period of use in
their world was kind of
slogging through my C++
doing the best they could,
but now is a very exciting time,
we're going to be doing
some very useful and
interesting things with C++,
and I'm very happy that this is my
native language when I program,
and it's been useful in so
many different contexts,
from writing code,
to bare metal hardware,
trading derivative securities,
to writing compilers,
the same language,
sometimes the different subsets has
been absolutely satisfactory,
and I couldn't be more pleased with all
these new language features.
So thank you.
(applause)
No threats from the audience?
Yes?
- What compiler new
versions do you support and
is the code available?
- The code is available,
it's really short,
as soon as you see the
first three lines you
say oh I know that, you
can read it yourself,
Stevedewhearst.com,
it's on my once weekly column,
once weakly, spelt W E A K,
because I updated about
once every two years,
there's an article,
I think I call it Template
Trees or something,
there's a little paper about
it and then there's some code,
I wrote some time ago, I
think it needs some upgrading,
I think it's in C++ 11,
I might have done 14, I'm not sure,
but you can probably find ways to
improve it with some of the
nice new features in 17.
Yes?
- I met something similar
to what you describe here,
I really liked it,
but the problem I found with it was, was
it was trying to beat all efforts at
trying to make things easier,
pointed me to wherever enable if is.
- It points you to enable if?
- If I get an error that says,
could you put enable it here,
and this is where your error is,
so please fix it,
and I have to chase through 100--
- Yes, that's hard,
yes this is always the problem,
especially when you're
teaching a new student,
someone who's used to a
less complex language,
they'll be using a template
and will be getting an
error in the middle of the
standard library somewhere,
but that's a problem,
so what do we do, well concepts,
but aside from that,
concepts are going to solve disease,
and things like that, they're great,
but in the meantime I'm a big fan of
using static assertions,
and it seems you're
making the problem worse,
if you have a static assertion at
various points in your code,
I'm already getting 26
pages of error messages,
why would I want another error message,
answer, it's the only
one you'll recognize,
so if you use static assert
liberally in your code,
when I say liberally it's
not a political statement,
when you use it liberally in your code,
it does really help,
because you'll find this
needle in a haystack,
and that's the best practical
advice I have right now,
we all know how to debug
really bad template problems,
the usual thing you
do, is clear your mind,
take the lotus position,
put your hands over the
keyboard and hpoe the answer comes to you,
last resort is to read the error message,
but if you do read the
error message it's nice to
see one of your own
messages in there somewhere,
it saved me a lot of time,
but again the nice thing
about concepts when
they're widely available,
is that they will catch that
error early in many cases,
but we're still always going to have the,
what do they call it,
the error novel that
you get from templates.
That's why we have job security right now.
Yes?
Mr Price.
- I wanted to hear, I
was gonna make a comment,
I want to hear your opinion on this,
my thinking was evolving toward what you
were presenting on this,
and then on the way I ran
into some speed bumps,
I'm preparing for my talk
writing code examples,
and I wrote some very C++ 14 code,
and I had readers with far pointers,
and other stuff,
the functional role was
derived at the right type,
and auto always give me the right type,
I didn't have to worry
about using the right type,
it was amazed at how easy it was to write,
and then I also wanted
to benchmark it against
some code I find on the web,
and I took their code,
and output also there's
and it had the same things,
iterators, mark pointers,
reference counter pointers
and so on and other things,
and is also written in the same style,
and I was amazed at how hard
it was for me to understand
what was going on.
In the same code.
- Yeah, that's a very good point,
and the only side-effect of
all these great new features,
is we don't know what's conventional yet,
I just gave a two-day
template programming tutorial,
I say and here's how you do this,
here's another way
here's another way,
there are five or six
ways to do everything,
so nothing is conventional yet,
so we don't know what
we are deviating from,
when we do something differently,
so I suspect that is something
that will be cured over time.
The question often arises
where do idioms come from,
we create the idioms,
we create them, actually Fador does,
he creates all of them and then they
spread throughout the universe,
but it's very Darwinian,
a successful idiom that
solves problems that
facilitates communication that makes code
understandable and simple,
shared the same way the
design patterns are,
and eventually takes over,
and the nice thing about
idioms is of course that
they modify over time,
they adapt themselves,
if you think back at
various languages that
were very popular and very useful,
and very well designed any given time,
they actually died away,
it's not that the language got worse,
it's that the problems changed,
the problems that it solved changed,
I think C++,
I would like to talk to Bjorn about this,
I don't know if it was intentional,
but possibly was C++
language is a substrate,
we don't really program in
C++, we program in C++ idioms,
the language evolves very slowly,
but it's a very rich
collection of features that
we combine and make idioms,
and use at that higher level,
and the thing that is ultimately what
makes C++ easier to use
than some simple languages,
we all speak a natural language
more or less correctly,
although I've been criticized on that,
and natural languages are
infinitely more complex
than even a language like C++,
but none of us has any problem with it,
and part of that has to do with idiom,
part of it has to do
with understanding the
structure that are
overlaid with these atoms,
that are words and what not,
enough theory,
but I think that particular problem is
going to be less severe
as we decide what is good practice,
but that could take years.
Yes?
- Just a comment from
the previous question,
I think there was a talk
couple of years ago called
Portable Static Service,
the whole talk was about using
stack service in combination
with a couple of idiomatic
techniques to reduce the size
reduce the template knowledge,
the error output you get sometimes.
- Yes I'd like to see
that, my experience is
that it doesn't reduce the
size, it just adds to it,
but at least some of it is readable.
Okay, there is a piano
here, if anybody wants to.
I was told to announce that,
I should have announced
it at the beginning,
it was just tuned.
Okay, thanks for coming.
(applause)
