Cookies   I display ads to cover the expenses. See the privacy policy for more information. You can keep or reject the ads.

Video thumbnail
- 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)