- Okay so, we're about to get going again.
So intro slide is the
same as the last session.
If you weren't there, well, that's me.
I'm presuming that I have
a lot here or a subset
of the previous audience,
because...
Quick outline of the talk.
First of all, very quick
summary of what is a contract,
that was a subject of the previous talk.
And from there we're
going to go into the idea
of how contracts can
actually be expressed fairly
directly in some parts of the C++ language
and whether or not that helps us simplify
how we document our code and
if we make those assumptions
some of those implications.
So I'm going to start
with my basic definition.
What is a contract?
And for the purpose of this
talk, contract, as I said before
is an exchange of promises
between an API supplier,
because now we're just talking about C++,
we're going to assume
you're supplying a C++ API
and their clients.
The client promises to satisfy
all documented preconditions.
So that will be our term of art
to say this is the client
side of the guarantees
I need to give by making the call.
And the supplier will then
guarantee their post conditions
that they will deliver assuming
all those preconditions
have been satisfied.
And we went into this with
the assumption that contracts
should be stable after release.
It's a point of, seems
very simple and it's going
to have some meta notions
as we come through
some of the implications of the whole idea
of expressing contracts in code.
And as we spoke about at
the end of the last session,
there's safe way, relatively
safe ways that you
can revise a contract
after it has been published
with minimal risk, but
rarely with no risk.
And some of the assumptions
that people will start making
on the things we're talking
about gets into some gray areas
that you should be aware of
when you're thinking
in terms of stability.
So which comes back to
my basic question here.
How much of a contract is implicit
in a function declaration
or a class definition?
If I have a signature, void
swap in ref x, int ref y.
What assumptions can I legitimately make
about this code in the absence
of any other published information?
There's already, looking at this code,
an implicit contract.
One of those implicit contracts
is I have a function called swap.
It takes exactly two arguments
that must be lvalues.
These are things that the
language and the compiler
is going to handle for me so I don't need
to go out of my way to unduly perhaps
document some of these.
I'm going to assume x and
y refer to valid objects.
I'm not going to need
to make that requirement
when I document my version of a contract,
because if x and y are
bound to invalid objects,
you already left the well defined behavior
of the language before you ever got around
to making my call.
So I do not need to document
that when you call me,
the things you give me are valid according
to their own rules.
And references come with
some basic guarantees
in the language and one
of those is I'm referring
to an in lifetime object or function,
but in this case we've got integers,
so they will be an in lifetime integer.
Potentially, I guess I
could be having to deal
with uninitialized integers and that,
yeah, maybe I may, got
myself a bit too cocky here,
but yeah, generally speaking,
if I have a reference I'm going to assume
there's a valid object at the other end
of that reference and it's the burden
to document otherwise if I'm going
to handle uninitialized ints.
There's no result because it returns void.
So there I'm just thinking in all abstract
that I can't auto equals and
expect that to bind there.
I can't suddenly come back and change this
and start saying ah ha, my
function gives an int and...
Or if I had a function that returns such
I couldn't change it in that direction.
And because I say nothing with
that exception specification,
I have to assume that
this function might throw
by throwing an exception.
I'm making big assumptions
just from the naming
what it's likely to do
and I would be shocked
if it threw an exception.
But I can't make any implicit
assumptions on the code
that we see here.
But when I come round to
documenting my function,
I have to at least guarantee the semantics
of the function, that I'm
going to exchange the values
of x and y.
That is probably sufficient guarantee.
Though if we really wanted to be serious
about saying you can't throw
that would do no harm to provide
that as an additional guarantee.
So my principle going into this talk,
is that not every aspect of a function
needs to be documented
in the English language
contract I was talking about
in the previous section.
The unspecified parts of the contract are
what gives us freedom to
evolve moving forward.
If you do not document
explicitly what you mean
with these signatures though,
even down the nitty gritty of some
of the language implications here,
clients will make assumptions
about that undocumented
gray area when they're reading
your functions and signatures.
Sometimes those are the
implications you want them to take.
If you think there's a
chance they will misread
those implications, even though
the signature looks good,
that's where you could jump in
and provide additional documentation.
Which is the bullet that I just put up.
If we look in C++98 going back in the day,
there's a few features
here that the language
gives us relatively direct support
for expressing important notions
that we would want to
write into our contracts,
certain guarantees about
the properties of types
and how you could use them.
The fact that we have types at all.
And these are a concise way of expressing
unambiguously perhaps, some of
the notions of our contract.
So we want to take full advantage of those
to hopefully produce simpler contracts.
We'll start with the first one.
What does const indicate?
It actually could indicate
three different things.
If we look at the first
case, it says very clearly,
x is a constant value.
It can never change.
That's a very nice thing to rely on
and it's very helpful for the compiler
to be able to rely on this as well.
And it's very hard to violate and break,
certainly within the
rules of the language.
So, I don't need to wax lyrical,
it's like having a comment
that says increments are i.
I don't need to say anything to a user
to know that if this thing can't change
we've got some set of properties
we can already assume about it.
On the other hand, if I have
a function that takes const,
a type by const reference, what I'm saying
is the function I'm calling is not going
to change that value.
That's for me as the caller.
I've got that guarantee
from the code I'm calling.
But when I'm the implementer
of that function,
I can't assume that
that function might not
have its value changed out underneath me.
Of course, for that to be happening,
it's likely to be occurring
through multi-threaded code,
maybe we've got a synchronization issue.
But fundamentally, I have a constant view
on something that might change.
In particular, I've got aliasing
within my own function call.
I might have a multiple reference
into what that object is referencing.
It might actually change underneath me.
So, I've got some assumptions here.
Perhaps I need to put
additional aliasing assumptions
if I'm going to rely on
this thing not changing
or threading assumptions.
But we have some strong notion still
that const reference is giving
me some useful information.
And finally, if I've got a
const qualified member function,
I know that if I call that function
it's not going to change
the state of that object
unless people are Qting with mutable.
So it's kind of a guarantee.
It's a function that gives me permission
to call functions through
the const references
so the language can
enforce some of those rules
and apply contract
validation on my behalf.
So this is definitely a basic tools of C++
and trying to throw the
language of contracts of them,
so to try to see how we can easily grow
out code using contracts.
The other example we just looked
at was pass by reference.
I get to make the assumption
that you have given me
a reference to a valid object.
Otherwise you've already
entered undefined behavior land
before you got to my function.
So I don't need to worry
about that reference
referring to something invalid.
The program was out of
contract before you got to me,
so it doesn't matter
what my contract said,
the program's already undefined.
Conversely, if I pass a pointer
I have far more undefined
behaviors to worry about.
The pointer could be
pointing to an expired
or invalid object and it's
only when I dereference
the pointer within my function
that the undefined behavior occurs.
So by passing a pointer, I move the point
of invoking the undefined behavior
to within my function.
So I do have to worry about that concern
and document it to say, hey,
I'm going to dereference
this pointer, don't give me a bad pointer.
If all I'm going to do is
copy and move it around,
I might not need to worry about acquiring
that guarantee on the caller.
There's also a common
invalid pointer value
which is the null pointer value.
You'll find code bases tend to
perhaps do different things with this one.
There's a common convention
that functions can't handle
null pointer values
unless they say otherwise.
And there's other code
bases that think, no,
the null point is a perfectly good value
if I don't tell you I can't
handle null pointer values,
it's my responsibility,
since I didn't say anything,
to test that you didn't
give me null pointer value,
and handle that special case.
And at this point, we're leaving the world
of what the language can give you
and we're into convention.
It's important to
understand the conventions
of the libraries you're dealing with
and how they are documenting
their own contracts.
Another obvious part of
contracts in languages,
types themselves are
fundamentally a contract.
You have a certain bundle
of properties and operations
you can assume when you have a type.
It gives you a vocabulary
you can just drop
into your functions that brings with it
an awful lot of implicit
guarantees and contracts
that come from that type.
'Cause otherwise it'd be
impossible to document
everything and every function everywhere.
This is how we, this is our main vehicle
for simplifying contracts.
We use types and trust the
properties of those types
as they go through.
Type violations are going to
be caught by the compiler.
If I try and use types of
manner that are incompatible.
If I...
There's a notion here, I'm
trying to read my slide up here,
I should be reading it down here.
What is this thing about
copyability and convertibility?
What's that?
But yeah, certain properties of type.
If they're copyable,
if they're convertible
between each other, you
know these properties
about the types themselves
and just putting them
into the function's signature
brings those guarantees with you.
Can I call this function with
this value or this object?
Well, if there's a conversion between that
and the argument type, I
don't need to spell that out.
That's already buried into the language.
Which brings us to the
notion of user defined types.
How we can now extend the language forward
rather than just using
the built in scaler types.
Classes and unions allow us to assemble
much larger, more interesting
and complicated types
with their own sets of
constraints and guarantees.
And again just coming back
to general good software
principles that have
stood the test of time,
a helpful principle is a single class
could have a single responsibility.
And you build larger classes
out of simpler things.
At some point you might
feel that the complexity
of having so many classes to
manage requires some other
simple organizing scheme, but in general,
single class, single
responsibility will go
a long way to giving you
comprehensible, reasonable code
in a manner that you can reason about it.
Like it's unreasonable
not to do these things.
And again enumerations and
arrays give us different aspects
of how I can play around
with the fundamental types
in a language to give
additional information
for contracts without
having to get too buried
in specifying a lot of these details.
So use them where appropriate.
Constructors are one of the key components
to the user defined aspect
of the C++ type system
that I'm going to use to
ensure that my contracts
are being managed and enforced for me.
A constructor is my opportunity to say
when I'm dealing with this type,
it's the constructor's
responsibility to guarantee
that that object is now in a state
that has satisfied all its invariants.
It's my job to document
what those invariants are,
you need to know what you get from
and document what state that constructor's
going to put the object into.
But the object doesn't exist
before the construction.
If the construction
fails, typically I'm going
to throw an except heat and
never get the object alive.
So if a constructor completes,
it's my responsibility
to make sure honoring the language side
of the contract here, my class
is now in a usable state.
But this now guarantees objects are always
in usable states and
that allows us to make
all those assumptions
as we pass these objects
through our API's that unless
there's spelled out states
that certain operations
have preconditions on them,
I can always assume my
object is in a good state.
And I don't need to further document that
as clients of code with objects
of these types coming in.
Now one of the idioms you
might get in other languages
is the notion of partial constructors.
Partial constructors really
don't work well for C++.
Because that leaves you with an object
that's now living and breathing,
but it's not in a valid state.
It's breathing very raspily.
I strongly urge you not
to get into the notion
of partial constructors
and two phase construction.
There is a notion once we have C++11.
That we might like to
have partial constructors
that the public constructors
can delegate to.
In which case you might make
your partial constructor
a private constructor, but
it's still surprising idiom
that I think is going
to fool enough people
and especially when you
come round to the next
slide on destructors,
you now have to handle
partially constructed objects.
It's usually not a great idea.
So I mentioned it in passing
mostly to say don't do that.
And I'm always nervous
when I put a don't do that
on a slide that people,
oh there's this thing
I didn't know I could not do.
Now I will go and do it.
Don't do that please.
The other key aspect for user
defined type is destructors.
They've got two key properties here,
the first is they're going to guarantee
when my object is destroyed,
it's my responsibility
as I implement that
destructor to say okay,
I have a living breathing object,
I'm now going to release all
the resources that object
has been managing and now my guarantee
that my program is going to be leak free.
And this is the other essential aspect
of wrapping components
and knowing how we can
use types safely.
It's, in terms of my previous slide,
destructors are acting as
our postcondition guarantees.
The postcondition is, it's my tool
to weave my object, my
system in a cleaned up state.
There's another interesting
property you can get
from destructors which is
using classes as control flow.
Because a destructor is
always going to be called
regardless of whether a
function exits through a return,
an early return, a late
return or an exception,
sometimes it's useful
to embed clean up logic
for a function of some
other aspect directly
as a piece of logic in a local object
that's got a destructor that
just says I'm guaranteed,
I'll run that cleanup as I exit.
Well it's unusual that you're not
actively managing another resource
from a class that came
with its own constructor
to do that work in the first place.
And the whole notion of
constructors and destructors
wrap up together, think of it as a notion
that has been referred to as RAII.
Resource Acquisition Is Initialization.
It's a idiom we got from
Beyana who always complains
he's not very good at coming
up with memorable acronyms.
But it's a tried and tested technique
of the C++ community.
The whole point of using this is
that our resources are
now never unmanaged.
And that's part of our contract,
that we can see these types going
through the type system.
There's the implicit
invariants allow us to reason
about our contract without
having to dictate too much
about micromanaging all those resources.
And the principle is resource
acquisition is initialization.
Always wrap any resource
acquisition operation
in a constructor.
Have your class manage a single resource
and then you build your
larger aggregations of classes
out of the smaller ones and
that guarantees acquisition
and cleanup never end up racing each other
and you end up with strange corner cases.
Once I have my objects this way
we now have the ability to transfer
and move objects around, exchanging state.
And these things always now stay
in these managed types.
So it's the type that
carries that exchange wadding
of the contract, rather
than me having to write
lots of fine grain contracts
that are explaining
how stake is moving
between different parts
of the system all the time.
I can simply say,
these objects transfer around
and the stake is managed.
Greatly simplifies
reasoning about contracts.
Another key contract support in C++98
is the notion of virtual functions.
Now I don't want to get
deep into the notion
of Liskov Substitution,
how to use substitutability
correctly, there's an awful
lot of design discussion
at this point and it's exactly that.
It's design discussion.
Whereas I'm trying to focus
on this is a mechanical tool
that just allows us to
express certain kinds
of customization within a language.
So what do virtual functions give us?
Well, we're going to
document our virtual function
so that gives our client the ability
to make certain assumptions
that we've contracted.
But it also allows them now to plug
in their own extensions that implement
that contract for themselves.
So this is a way almost
of injecting contracts
back into our clients to say,
I want you to give me
something that satisfies
this contract, I've
implemented my side here,
but you've now got to
implement this thing yourself
that also honors the contract.
So it gives us a two way communication.
One of the nice things here of course is
the compiler's going to
ensure that the signatures
match correctly, allowing
covariant return types.
If these things don't match
we'll get various errors
coming out of the compiler.
Always nice to use the
compiler for our benefit.
The key point is, once
you're into the realm
of virtual functions, your
ability to specify a contract
becomes a lot more important
because both client and
supplier are now relying
on both sides of these contracts.
And abstract base classes are an extension
of the notion of virtual functions.
This is just a common idiom to say
we've distilled out contract down
to as simple as it can,
as primitive as it can be.
I've not actually gotten implementation
behind this function.
All I've got is the contract
and this is my mechanism
to pass you a contract that,
you either give me
something that fulfills that
and I can use it
or we can plug in these
extension points quite handily.
By example here from C++17,
the PMR memory resource
in order to back the support allocation.
But as we start getting into the realm
of the abstract base
classes, classes in general,
there's still a burden
that we have to document
how the contracts of use
of a class as a whole,
the inter-operations between the,
the operations of the class.
Too many operations there,
relate to each other.
And my final aspect of C++98
is the assert macro that
we inherited from C.
This is moving much
more onto the other side
of the equation to say this is a tool
to help us enforce contracts
at runtime in the client side.
They're not evident to
a cause of the code,
but they are still a very useful tool
in expressing contracts through our code
especially for the
maintainers reading that code.
They give an account
to instrument our code
with the ability to say
I can build in one mode
where I'm going to perform these checks
and try to validate the
preconditions that you give me.
And I can build in a nice release mode
where all that code just simply vanishes
so there's no actual
performance runtime cost of it
at the expense of I'm not
going to catch your errors.
And the system's then going
to go into undefined behavior
if you've lied to me.
So it's a support tool.
It's a really nifty tool
when you're testing.
I've noticed that in some
development communities
they really like wide contracts,
and they distrust things like assert,
that they're going to kill their program.
That's not what they want
happening at runtime.
But in general this is the
best we have with C++98 and C.
And when I first started using this
it was shocking how many bugs it found
in the first two, three weeks
and how few it started finding after that.
The surprising, the idioms
that you have in your code
that you're not aware that you've got,
this kind of tool
quickly flushes them out.
Hopefully speaking, preaching to the choir
on that one by now.
Then we move on towards C++11.
And we get a more extended variety
of language features that could help us
trying to express our contracts
more directly in code.
And I think usually gonna
stop making assumptions
about what we're writing here.
So, a classic one, we
added the override keyword.
So this is a way to say yes, I'm actually
forcing a contract from my base class.
I am trying to override a function
that matches this signature.
Unless I find that in
one of my base classes,
this is now an enforcement
technique to say,
yes, I will catch that contract violation
and the compiler will tell me about it.
And as a client reading that code,
I know that there's some
additional documentation
I need to go and read in the base class
that might not be all
entirely described here.
The other aspect we have
with virtual functions
is the notion of final.
And this lets readers
know that at this point
nobody else is allowed to
further modify this operation.
So when I'm reasoning about it,
I can now reason about it as
if it were static dispatch
rather than dynamic dispatch.
Which is a nice thing as
the reader of the code.
It's a really nice thing if
I'm a compiler of the code.
And we can apply this either
at the whole class level
saying users are no
longer allowed to derive
from this class, which is an
interesting kind of contract.
It doesn't play as well with
C++ as some other languages
as C++ often goes with the
notion of mix in classes
to build more aggregate functionality.
And if you've got a final
class you can't wrap
that so easily.
But final on virtual functions
is certainly a nice way
to give the guarantee
that yes, I'm at the end
of the current aspect and you can reason
about me much more easily now.
One of the more intriguing parts of C++11
that to this day I still find
people having interesting
discussions about, I'm
hoping again that we've got
a room that largely knows
the notion of move semantics.
If I say move semantics, is everyone happy
with what they think I'm talking about?
Well, I'm not getting
that many hands going up,
so I'll take this one at
a reasonable pace then.
So move semantics are
basically a library convention
even though I'm putting them
in this language support part,
that are built around
the new language feature
called rvalue-references.
So with the C++11 compiler,
the compiler itself can't
enforce that convention for you,
but it's given you the language facility
that you can build this exchange around.
And the principle is if I have
passed an rvalue-reference
I'm going to assume that what
is on the other end of that
is either an rvalue, it's an
object that's about to expire.
So nobody will need the
state in that object
after my function has finished executing,
because I'm bound to a
temporary that's going away.
And if it's not really an
rvalue because somebody
has lied to me with a dastardly cast,
they want me to make the assumption
that it's about to go
away in the same way.
That's the notion of the convention.
So, given that I know that nobody can rely
on the contents of that
object after I have executed,
I'm actually quite at
liberty to change the state
of that object in any way I want.
Nobody's going to be able
to observe those changes
by and large.
So that allows me to efficiently transfer
and steal resources from that object,
rather than having to perhaps
make expensive copies.
This is the fundamental
assumption of move semantics.
But, I have to also
live with the assumption
that if you didn't give me an rvalue,
if you lied to me with those funny casts
that come out of standard move,
then the object that sits
around might actually
end up living beyond that function call.
What you've told me is you don't care
about the state of that object,
but I'm not allowed to violate
that object's invariance.
And this is where move
semantics gets a bit
controversial with some folks.
If I want to have the object
left in any random state,
it might not be valid to do any operation
on that type.
What I'm saying is that type
has really weak invariants.
And as long as I document
those crazy weak invariants
that say you know, if
you've called move on this,
if you've moved from this
object, all bets and off.
All you can do is destroy me.
That's a reasonable thing to do.
It's a viable thing to do.
I don't think it's a particularly
reasonable thing to do.
I don't like contracts that are that weak.
But in terms of when I'm
writing my end of the code,
I'm going to have to assume that anything,
any object I'm stealing
stake from is going
to persist beyond that
call in some shape or form.
At least a destructor has to run,
if it was a true temporary.
And other folks might still want
to use that object afterwards,
so the invariants hold if
there's conditional invariants.
So I've got something like a vector
and I've moved all the
data out of the vector.
I can't call front safely afterwards,
because I don't know if
the vector you've given me
back is empty, but I can
call the empty function.
And once I know whether or
not that vector is empty,
it's reliable that I
can call front or not.
So that's the essence of move semantics.
It's simply saying okay, I'm making,
taking inference that you don't care
about the state of this
object after I'm done with it,
but I still need to leave
you with a valid object.
I'm just taking my
opportunities to get my stake
into the, let's say I want,
as efficiently as possible.
Another neat guarantee that comes
with the language is noexcept.
We have the option you know, my function
does not throw exceptions.
I have the option of putting
noexcept on my function
to let everyone really
know that rather than,
or as well as writing it in
a plain English contract.
This is a guarantee that
the compiler can rely on.
It will enforce it at runtime.
So there may be a runtime
cost to putting this here
as the compiler now has to
potentially catch exceptions
in order to terminate when you've violated
your end of the deal if you
did try throwing through there.
It may or may not be
able to optimize it away,
if it can reason and see
well all the operations
you're calling can't throw,
so I know there's nothing to catch.
But in principle there's a
runtime call that we want,
runtime kit that we would
like to see optimized away.
This is one of awkward case, unusual cases
whereby contractually
putting this contract
into the code we can also
reflect on this property
'cause there's a noexcept operator.
So there's more interesting questions here
beyond just I'm giving
you a contract that says
when you call me, you know
I've got the no-throw guarantee
which is a really handy guarantee
to have about operations.
You can actually query
about that in generic code,
you know templates, you'll
therefore dispatch code
accordingly saying well if
I know that this can't throw
I might be able to do a more
efficient set of operations.
But do think carefully
before placing this freely
throughout your code.
'Cause what you're doing is
you're writing a contract
that says this code can never throw.
Not only now as I'm writing it today,
but as I evolve this code into the future
and maintain it and it goes
through any other future changes
it's a binding guarantee
into the future as well.
So if that guarantee is good, use it.
But if that guarantee is
not something you want
to commit to yet, even
though you put it into your
English contract, you've got this freedom
to play around with it a little.
In particular, think carefully
about putting this on
what I was calling in the
previous session narrow contracts.
Because if you put this
on the function signature
it means your ability to
diagnose and help your callers
when they call you out
of contract has now been
constrained by the compiler.
If you just document you
don't throw exceptions,
you're perfectly (mumbles)
to throw an exception
if someone calls you out of contract.
If you put this on a signature,
the compiler's going to
enforce that rule for you.
If you don't care about
that, it's a great tool.
If you do care about how you're trying
to diagnose and help folks
out once they go outside
the contracts, that's a consideration.
Constexpr is another interesting feature
that has implications for contracts.
Provides a guarantee
that the function itself
can be computed entirely at compile time.
Which, among other things
that I didn't actually
get on the slide, means it's
what we would call side effect free.
So it's not going to have
any effect on the system
that persists beyond the evaluation
of the function call itself.
And once you know you have
side effect free code,
that's really handy in
a variety of contexts.
Especially in concurrency for example.
And we also have a
second important property
comes out of this so that those contracts
we want to enforce that certain times,
such as standard mutex
really need to be burned in
before main is running.
We don't want race conditions
as we're trying to create
some of these types.
And constexpr is a tool
that allows us to put
that contractually into our type.
You can use this type and
it will be constructed
before main starts without
actually running any code.
It will just basically come into being
in this perfect ideal
constexpr evaluating state.
The final part I have on the C++11 side
of expressing contracts
in code is one of those
wonderful joys that came with C++98/03
but really took flight with C++11,
which is the notion that I
can play games with templates
to cause them to fail
to match in name lookup
and overload resolution
if certain constraints
are not satisfied on the properties
of those template parameters
that are being deduced.
Hence we end up with wonder
acronym substitution failure
is not an error, we can't
blame Beyana for this one.
And we've still not found
a better term for it.
So again this is a way
of expressing constraints
directly in code which
is often a good thing.
We now have standard type traits that make
some of these expressions
much easier to handle,
so we've got is
constructable, is convertible,
is invokable in C++17.
As a failure to satisfy this
constraints is an indication
that yes, I'm going to,
if I try and use a type
that doesn't satisfy these constraints,
I'm rightly going to get an error
saying I could not find the function.
But it will fail with a friendly manner
so if another function with that same name
does allow this thing to be accepted,
we can go on and find that without
putting an eager error in there.
If we call it directly by
instantiating the template though,
it's still going to be an error.
It will cause that to fail to compile.
So we're now requiring certain
properties of our types
directly in our signature,
which potentially means that ya know,
that's our documentation.
We've put that constraint into the code.
We have in C++11 standard
library, standard enable there
is a convenient tool for packaging up
how we can express a
lot of these constraints
and trigger this SFINAE
condition much more easily
than trying to play all
these games by hand in code
because truly expert domain as opposed
to modestly expert domain.
My own personal opinion of this is
that lots of complicated
SFINAE constraints
are generally much harder
to read than actually
the plain English contracts
trying to say the same thing.
So yep, we can put this
into our code it means
that the compilers help
us enforce our contracts,
but it might not be the
best form of documentation.
So we couldn't really done
that, we specify it in English.
It then just gives the, if the English
and the implementation are in conflict,
which one was correct?
And that's just your classic bug report.
Did I get the contract correct?
Or did I get the implementation correct?
And as I was saying in
the previous session,
changing contracts is
generally a lot harder
than changing implementations.
Assuming people have written
to the code correctly
and my battery's running out, so excuse me
while I remember to actually
plug in the notebook.
Knew I brought this for a reason.
And the last tool that we have with C++11
is static_assert.
And this is just another
tool that lets us validate
and check our contracts.
In the same way that
assert checks at runtime,
this allows us a compile time check,
mostly within our implementation
so it's not as visible to the end user,
but they're trying to
call us correctly with,
through our types information.
Static_assert does have the ability
to leak out to more public facing aspects
such as at a class scope.
That might be deemed as a
form of documentation there.
But mostly this is a
contract enforcement tool
rather than a contract
implicit documentation tool.
Do I have any questions on, boom.
Wrong way.
The C++03 and 11 tools
that I'm mostly assuming
we're familiar with before I move on?
Okay.
I'm afraid I was slightly gambling,
we're gonna take a bit longer
and I've not done, whoops.
Too much detail as an in
depth guide on concepts
because there's gonna
be much better talks.
They have actually got a
whole talk given to them
in this session, but
concepts are an important way
of expressing contracts.
Those of you, we'll see above,
I've now put concepts replacing
the how SFINAE trickery.
Because concepts are a much,
are a new mechanism in the language
that much more cleanly allow us
to describe constraints on the operations
that we're trying to document
when we're writing our templates.
In general, the syntax
is clearer and simpler
than the last SFINAE
tricks because it doesn't
have any funny template
machinery obfuscating it.
And we've got a much
more direct vocabulary
once we start defining our concepts
to spell this out so it
reads far more cleanly.
And it also provides certain
context where it's much simpler
to provide the machinery with concepts.
It goes into places that
weren't as simple as C++11.
In particular, if I've
got a class template
and an operation like a
constructor that itself
is not a template.
It's really hard to set
up a SFINAE constraint
on that in C++11 or even 17,
whereas it's really simple
as we're about to see,
to drop a requires clause
on there with concepts and provide that,
provide that documentation
and implementation there.
And the other key thing
is once we have concepts,
we have names to repeatably
use the set of constraints
that opening up vocabulary
really simplifies
how we talk about our code.
So just a couple of simple
examples I saw straight
out of the standard.
This is an illustration of
what the syntax looks like
defining a concept.
A concept is basically a predicate.
But I was trying to, ya know,
define the is_same predicate.
It turns out it's actually
simpler to define it
in terms of the type trait.
But you've now, having it as a concept
now allows us to drop the same constraints
much more easily into
the code that follows.
Similarly, if we look at my
convertible to constraint,
it relies on partly the type trait,
and it also requires that
I've got this valid expression
that, it's so, not quite (mumbles),
especially asking the
compiler, does this expression
compile the way I expect?
So here's an example I was talking about
of the kind of code that's
really hard to write
in C++17, but it's really
simple to write with concepts.
I've got the pair template,
I've got parameters T1
and T2 for the pair.
But those are template
parameters on the class template.
I'm now trying to provide the constructor
that takes a const t1 by reference
and the const t2 by reference.
And the requirement here
for this constructor
to be valid is I need the
elements I'd be copying
into my pair to be copy_constructible.
Otherwise I don't want this
signature to match at all.
And it turns out this is really hard
to eliminate with C++11/17 syntax,
just falls out very easily
with the C++20 concept feature.
Here's a simple example of just trying
to convert a simple notion of using,
we can put concept names into
the template parameter list
so things fall through from the template,
it's a simple syntax example.
But it's the notion that
a lot more can be divined
just now from the signature because I'm
putting the vocabulary of
concepts into the declarations,
there's much less burden to
describe the nitty-gritty
of all the operations
that come with this now.
Just another example that
was plaguing me last week
when we would try to clean up
some parts of the standard,
in the library working group.
We got this annoying notion that,
we look at this constructive of vector,
it's got a requirement that
it's an explicit constructor,
but it's an explicit
constructor potentially
in two arguments.
And we've had a general attempt
to try to clean these up.
But what we want to do
has now split itself
into two constructors
so that one just takes,
the explicit constructor's
taking size_type n,
and it knows how to handle
the missing allocator
by just passing it through.
And another thing is we want to do stuff
through the delegating constructor
and make that a specification.
And this cleans up quite
nicely once we have
the requires clause.
First we can have the
missing requires clause that
my
my template parameter is
default constructable.
Yes, because we've been making
n copies of this default
constructor thing, I do
require that the element
type in the container is
default constructable,
otherwise I can't use this constructor.
But now when I do the
delegating constructor
I end up with the requires requires clause
that just says, yeah,
I'm not going to repeat
all the constraints in the constructor
I'm delegating to, I'm just going to say,
I'm just delegating to
that constructor over there
and I require that the same
things that work to call it
are still valid.
So this kind of delegation pattern means
you might start seeing a few
more requires requires clauses
around, but again it helps
simplify a bunch of getting,
breaking apart some of these old APIs
and providing a cleaner, hopefully attempt
to express some of this vocabulary.
Final part we want to
come to is the notion
that when you start talking
about contracts in software
we've got these new
attributes coming into C++
that specifically enable
checking of the runtime
evaluation of contracts.
So this is the new analog
of the assert macro
built into the language so it it's parsed
and hopefully provides us a
richer, more useful tool set
to reason about these
things in the future.
So we have an expects
attribute that specify
preconditions, an ensures attribute
to verify postconditions
and then the assert
that can check whatever we want.
The difference is the
expect and ensures go
on the function declaration
and are therefore visible
to the cores and the compiler.
The assert just goes in
the function definition,
so is not as obvious to the
people calling your code.
It helps verify that
the English is correct.
Here's thinking what do we do if we fail
one of these contract checks?
I've not showed you how to write yet but,
building up to where we're going.
The contract check fails
we're going to call
the system violation handler.
And by default, that's
just going to call abort.
And hopefully write out the information
about the file and line and
the expression that failed.
But fundamentally it's going to terminate
your process and call abort.
But you have the ability using
implementation defined means,
so not 100% portable, but in principle
there's always going to be
a way that you can supply
your own custom violation
handler to be called instead.
So you might wish to
report or handle violations
in some more specific manner.
One of the tools we use here at Bloomberg
is we turn our except
violations into exceptions
so that we can test our code by saying,
did you throw the I violated the except,
the expression you expected
me to violate exception?
And if that doesn't throw
I know that I don't have
my appropriate checks in place.
But perhaps not what
you want in production
is turning precondition
violations into exceptions is,
not necessarily the best
answer for a production system.
So we'd write that handler,
there will be a tool
to supply that on the command line.
We have no programmatic
access to that feature
in C++20, and that was deemed
to be an important feature
by folks concerned by security.
They don't want to have
the ability to have,
we've got this wonderful tool to help
that we're being caught out of contract
and if that's now a call
back that can be subverted
by a hostile attacker,
that's a really bad thing
to put into our code.
So we do not want this to be
inspectable and changeable
at runtime by a hostile attacker,
so it's completely a build time artifact
and once it's in, it's in.
There's an additional assumption
that your violation
handler is either going
to terminate and tear the process down
or it's not going to return
by a regular control flow
so it might throw an exception.
A second suite says,
well, we'll actually allow
the check to do whatever
it said it was going to do,
and then continue running the program
into our potentially undefined behavior.
Why would we ever want this?
Well, if I'm trying to apply
this new contract facility
onto an existing live system,
I'm concerned that my system,
that I've not run against
these contract checks
might start failing and crashing
and dying horribly.
I want to have some notion of the impact
that this change will have.
So I install a violation handler
that simply logs, hopefully with some kind
of exponential back hopping
case it hits something
in a fast loop and then returns
and allows the program to do
what it's always been doing.
'Cause this is a live system that's been
in production for years.
And that gives me a
chance to audit my system
before I actually deploy
into production code
that actually uses such
checks in the future.
The other thing that comes as a dimension
of our ability to control
how we check the code
is we have the notion of build modes.
If we think back to standard see_assert,
we've got the disabled
mode where checks simply
don't run and what I call the default mode
that says, yeah, we're
gonna run the checks
you told me to check.
But we're also allowing for
a more expensive audit mode,
which says, ya know some
checks might be so expensive
that even in a regular bill
I don't want to run them.
I might be having a function like
find me the median.
If my precondition is the
whole range is sorted,
my test in the precondition's
far more expensive
than running the function.
So I really don't want
to have that by default.
But, in an audit mode, we're
having some really awkward
problems I'm trying to find,
have the ability to turn on
those more expensive checks
that I can mark as expensive,
turns out to be really useful.
Which takes us to the other side.
How do we annotate
these attributes to say,
well this is the level
I want you to check at?
And these again come with three flavors.
The default is what you
get if you say nothing,
but if you want to say it,
you can spell it default.
The audit is how I say this is going
to do one of those expensive checks
that I do not want to run unless I request
that I run the expensive checks.
And finally, we have
the notion of an axiom.
And an axiom is a check that
there is no way to turn on
for runtime evaluation in the compiler.
So what use is that?
Well, it turns out to be incredibly useful
for static analysis tools
and other reasoning systems
that might know the contract
of the function that
you're saying your calling,
which you might never actually define.
It just needs to be a
signature with a contract
that has certain properties that says,
yeah, that's a valid pointer.
I have no notion of how
I'm going to implement
a generic, perfect is_valid
pointer function portably,
but I can have an API that
says, yeah, here's for an axiom.
I'm asserting that pointer is valid
and people now know how
to reason about this
and that can lead into reasoning tools
for as I say, static
analysis tools and so forth.
The key thing is, because
we're now no longer
dealing with macros, but
features built into the language,
regardless of the build mode,
all of these attributes
now must parse correctly
and I believe that includes
passing name lookup
because I need to go find my use pointer
to have my notion of
what that contract was.
So quick example using
the expect attribute.
So, expect, as we expect, is an attribute
that says this is the precondition
that must be satisfied
before you call my function.
So here I've got a typical
smart pointer dereference.
Returns t reference, my
pointer operator star const
expects that my data
member m_ptr is not null.
Yay, my function's noexcept.
And because my function's noexcept,
the question now arises,
say I did my wonderful testing thing,
I've installed my throwing
violation handler.
If this function triggers
the assert check,
and that assert check throws,
is it going to throw before
or after I've entered
the protection of the noexcept?
And to keep things simple
and easy to reason about,
the rule is simply that
if I've got a function
marked noexcept and something throws,
even if it's one of these
checks, I'm going to terminate.
So hopefully that stays simple
and easy to reason about.
An example of using some
of the further attribute
syntax for the audit and axiom,
you can see where they
drop into the syntax
for give a median is sorted is something
I'd want running at an audit level
because that's quite
expensive, but a generic,
is this a valid range?
And knowing I've got two valid pointers
is not something I can
ever really expect true
runtime code to evaluate and
give me a correct answer for.
But it is something I would still like
to assert with an axiom.
So only at the other
end we have the ensures
which is how to provide
the postconditions.
So, hm, what have I got here?
I think I've screwed up my example
because I'm looking at min
and I'm seeing something
that looks like the
postcondition for sorted.
Yeah, that's why.
We'll jump down to swap first,
because I've got that one.
Once I've sorted my data, again,
I'm going to want to be
able to check the data
is we'll talking here in a
second, in a sorted state.
That's a linear cost operation.
I might not want to run that by default,
because even then it's n log n to sort,
provided the additional n might
still be deemed problematic,
we'll leave that at the audit level
and that's where audit goes in the syntax.
I've forgotten what I was trying to do
with that int min there.
It does bring up the other key example
I didn't put an example up
for, which is unfortunate.
We can also provide ensures clauses,
we give them a name
that can hold the result
to say is the result what I expected?
And that result might want to compare to,
say with a min I'm comparing my results
with the left and the, you
know, the rhs and the rhs.
It's got to be one of those,
and it's got to be the least of those.
The question is, what
happens if the function
modifies the inputs?
Because what I would really
like for the sort algorithm,
is to be able to say as a postcondition,
that my sequence is a
permutation of the input.
But once I've sorted
them I have no ability
to recover the original input
to get that permutation.
When I've got something
like the min function
where I've just got a
reference to two objects,
I can actually directly
compare them still.
But if they've changed, my
post condition could say,
I'm expecting I gave you the minimum value
that came in, or it could be
expressing the postcondition
that you changed in the way I expected.
So, when I refer to a variable,
do I expect it to be the
value that was there on input,
or the value that was there
when the function exited?
And it turns out that's a
really hard problem to solve
without producing far
more complicated syntax
than we wanted to get into at
this point in the language.
So the answer is it's undefined behavior
if you have a postcondition that depends,
whose result would change if
those values have changed.
They're not allowed to
change during the execute
of a function if you have a postcondition
that relies on them.
And I can see that my time is up.
So I'll quickly jump down, oops.
I had a quick summary slide
which has gone missing.
So contracts are essentially
guarantees that we write
in English, but people
are going to be able
to make assumptions from the code
as I was describing it.
You want to be clear to
your cores which aspects
of those contracts they can rely on
and which they can't, which
might just mean up front
documentation for your project as a whole.
What does it mean to pass a null pointer.
But that banter between those expectations
is also your freedom to
evolve code in the future.
So don't commit to more in the code
than you want to actually
be a reasonable assumption
of your client.
I'm thinking about things
like putting noexcept here.
And with that I'm gonna close up.
If there's any questions,
I'll be here to answer them
as we wait for, is there
another session after this?
Hm?
30 minute break so,
I'm happy to sit here and take
questions in the meantime.
(audience applauding)
- [Audience Member] So,
you might wanna stay here
for a second 'cause what I'm about to ask
is exactly what to do
with you were saying.
But so I'm giving a talk on Wednesday
about unit testing exception safety
and talk, we had a lot of
overlap and were talking about it
and one of the things, this
is I guess, more of a comment
towards axiom and just kind of asking
your thoughts about this.
But one of the ways that,
one of the reasons like a lot
of things you would mark axiom are axiom
is because you might need
to have as you were saying,
undefined behavior in
order to actually check it.
And one of the things that you can do is
like you may be able to
write up in your test build
something like for
is_valid_range is just like
iterate over the range
and make sure that you run
your binary with ASan
and then it yells at you
if you're doing something weird
and at least now you have some trace of it
as opposed to like some undefined behavior
that's just waiting to
bite you a month from now.
Is that like, is that
sort of like checking
like kind of part of the idea
of what was behind contracts?
- I missed, probably some
of, one of the other things
I should have mentioned
about the contract checking
annotations is there's
a fundamental assumption
that effectively pure
functions, they're not allowed
to change state, that's a bad thing.
And we assume they're always
well defined behavior.
As you say, you might
have your contract checked
for some defined behavior.
So if you were to run it
as a different level of,
declare it as something other
than an axiom if you wanted to force
that as an early check and
rely on some UBSan type tool
finding that invalidation for you.
But if I understand your question--
- [Audience Member] Well, I mean obviously
you wouldn't put axiom
'cause you'd want it to run,
but I was wondering if
that idea of forcing
undefined behavior is something
that was considered as like when contracts
are being thought of.
- There was a wide variety of discussion
where we landed on the
axis of undefined behavior
as we're going through this.
I think I want to catch you offline
to find out, I've not quite
drilled into the exact point
you're asking about but--
- [Audience Member] Yeah,
I'll come talk to you
when everyone else is done.
- I'm almost certain it was discussed,
but I'm not sure the exact
answer I'm trying to give you.
- [Audience Member] Cool, thank you.
- [Audience Member] So when we are talking
about using contracts in C++,
mostly we're talking about
verification of program.
But what about using C++
contracts for implementing
more optimizations and
optimization our codes harder.
For example you can avoid even,
for example to sort call.
If you have a postcondition is sorted
and exactly after call to sort function
we in caller we check is
our input range sorted?
We can remove this call or function.
- So, can we use contracts
basically for the optimizer
to make smarter calls?
One of the obvious concerns
is if the optimizer
can assume that any
contract annotation is true,
it never needs to check it,
therefore it will never actually
call the violation handler.
So we have to have some
interaction at that level.
We would really like the
guarantee that annotations
for checks even at levels
that are not running
cannot invalidate and cause
checks that you are running
to fail to evaluate.
Conversely, yes we do want
them to enable optimizations.
This has been a clear
desire, getting the feature
into the language for,
a wide variety of
churches trying to say why
we want contracts in language.
And then meta support for theorem
provers and for optimizers
was a distinct part of that.
Thank you.
(audience applauding)
