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

Video thumbnail
- So as I mentioned, I'm Bryce.
I work at, now NVIDIA, on this library here.
Come talk to me about Cuda stuff later,
if you feel so inclined.
And today I'm going to be talking about C++17.
As my little punch line here says,
an introduction to C++17 via inspiring examples.
So this talk, I've given a few times before
in different forums,
and a number of people contributed material in some way,
so, this whole list of names here,
and you can find the talk right now if you'd like
at this URL.
It uses this weird bundle of JavaScript
called reveal.js, which is kind of nice.
It's my first time using this for one of these talks.
So these are the 26 features
we're going to be talking about today.
So this is a two part talk,
so this first one hour session,
we will hopefully cover all of the language changes,
and then, the second one hour session,
we will cover these library changes right here.
And the ones that are underlined,
are sort of those that I think of
as more high impact;
whereas, the ones that aren't underlined
are sort of features that are there,
and that are nice, but maybe not as widely useful.
So, let's start off by talking
about the destructuring problem.
So that's, like how do we, like we have some point 3D class,
and how do we assign convenient names
to the components of this class?
So that's this structuring problem.
So for example, with, like we have this here,
and we want to do something like this,
and there's a bunch of different,
sort of caveats here, like we may want those names
to have reference semantics instead of value semantics.
Now you can sort of kind of do this with tuples in C++11
using std tie,
which you may be familiar with.
And there's kind of a number of problems with this.
Because the variable names need to be separately declared
before they're bound here.
So I've got my double x, y, z declaration,
and then I tie them down on the next line there.
So for example, you would not get any warning
if you had repeated names here,
like I'd accidentally written std tie y of y, z,
when I really meant std tie x, y, and z.
And if I wanted to bind a reference,
you can't really do this
because I can't have uninitialized references.
And you also, if you wanted to capture the const variables,
the double const here,
you can't have an uninitialized const,
so this doesn't really work
in the way that we would intend it to.
More generally, this pattern won't work
with any types that are not default constructable,
because they have to be separately declared
before you tie into them.
And also, of course, tie will not work
with your struct,
or things like std array.
You might also want to be destructurable.
So in C++17, we have a new language feature
called structured bindings,
and it allows you to take things that are destructurable,
so something like my point 3D class
or a tuple or an array,
and create variables that represent the components
of that object,
and give them convenient names.
So the syntax is auto, and then you have
square brackets, a list of names, and then,
the destructurable object on the right hand side.
So when I say destructurable,
the requirements is that,
either all the non-static data members of the class
must be public, direct members of the type,
or members of the same public base class of the type,
and cannot be anonymous unions.
Or, for any other types,
so some things that's like class, there's private data,
you can give it a .get method,
or you can have a get global function
that will match with that class high ADL.
And then you also have
to specialize these other two customization points,
tuple size and tuple element.
So in the standard library,
there is I believe only these three
types that meet these requirements.
The std array, std tuple, std pair.
And the auto in this syntax
uses the regular auto deduction rules,
so like you can write like auto const,
or auto ref, auto ref ref.
And that just sort of works
in the same way that auto does today.
So there's no real caveats there.
So this can be particularly useful
when dealing with things like std map,
which gives you back a pair,
which is kind of annoying to work with.
So this is, right here,
it's maybe a little bit hard to read, but
I've got a range based for,
when I have auto ampersand ampersand,
and then key value,
and then the map on the right here,
so I don't have to deal with any pair's ugliness,
which is quite nice.
Here's a, are these in the wrong order?
Oh, no, this is before and after.
So, this is the next feature I'm going to talk about
which is also
sort of about std map.
This example is,
so we often end up introducing variables
right before an if,
even if we're going to use the variable
only within that if statement
because the if condition is a variable,
so like right here, I introduced this auto it
in the outside scope,
but I'm only going to use it within this,
within the if condition within the statement
associated with the if condition.
Now in C++17, there is a new feature
called selection sequence with initializers.
So it's sort of similar to the syntax you're familiar with
for for statements,
and then you can do if,
and then an initialization statement,
and then semicolon, the if condition.
So I take that iterator that I created here,
and I can just initialize it right here,
and then it will be available in this scope right here.
So the syntax looks like this.
So if init condition, then you have your two statements here
and what it's equivalent to is
putting that init into a,
right before the if statement in a new scope.
So this is just like a little syntax hack.
One thing to keep in mind here is that
the variables and the initializer are in scope
for all possible branches following the initializer.
So in this example here,
I have an if init condition saved in here
where I introduced X.
So X is in scope here.
Then I have this else if where I introduce Y.
And both X and Y are in the scope here.
And then in the last else here,
both X and Y are in the scope.
It's a little unintuitive,
but like if you expand out this,
you can see why it works like this.
So this feature can be very useful
when dealing with locks.
Like, suppose I'm trying to implement
this class string pool,
which is supposed to be a thread safe,
sort of, like, hash of just strings
that have memory associated with them,
so that we don't have to go and allocate new memory.
So we want to implement the pop method
for this string pool,
and so we implement it like this.
So, first we have default constructed strings here,
then we go and try to acquire the utext,
and if the pool is empty,
if the pool is not empty,
we go and swap the last string in the pool
with this temporary string we've created,
and then once we are outside of this if statement,
we go and check if the capacity of the string
is smaller than the capacity that we've requested.
And if we do, we go and ask the string that we just got
to reserve a new one,
so if we didn't find a string here,
this reserves the desired capacity,
and then we return here.
So there's a bug here of course,
there's some performance bug in that.
We are holding this lock,
while we're doing the reserve here,
and that's not good
because that might be allocating memory.
You don't want to hold a lock
while you're allocating memory.
So to fix this,
you would put a scope around this lock guard
and this if statement here,
so that the lock is released
before you reach this branch
and the possible reserve right here.
But it's kind of ugly because you have to introduce,
you know the extra squiggly brackets.
I really don't like having
more squiggly brackets than I need in my code.
It makes it harder to understand.
So with this new syntax,
you can just put the lock guard
right in that initiate, right in that if statement here,
and the scope of lock guard is just right here.
It'll be this word right here.
The lock hoist.
And last one of these.
This I think combines very nicely
with structured bindings,
when you're working with maps.
So like right here I've got this in place
for throw functions,
so I want to try to put something into this set,
and if it's already in the set,
then I'm going to throw,
otherwise I just want to put it in there.
And so I have this if
and then the structured binding right here.
So that introduces that for the scope here,
and then we would go, you know,
based on whatever the key is,
we could create an exception,
and say hey this existing key was in the set
that you just tried to insert into.
So this syntax is also there for switch.
So you can do switch, init, condition,
and it's sort of the same mapping right here.
And I don't really have an example for this one,
so this one's a little bit less frequently used,
it was difficult to come up with a good example for this,
but it is there if you need it.
Alright, so next I want to talk about if constexpr.
So before C++17, when processing variadic parameter packs,
we would often implement an interface like this,
where I want to say, hey,
here's a, you know, a parameter pack of things
I'd like you to print.
And we would implement this by having a base case,
and then a recursive case.
So anybody who's starting
any amount of pertinent variadic parameter packs
is probably familiar with this pattern.
In C++17, we can write this in just one overload
using if constexpr.
So we don't have to worry about the base case recursion,
because what if constexpr does here is,
if this condition's not true,
then this statement is,
needs to parse, but is not instantiated.
So the fact that there is,
that when you're on the last element here,
but there's no function overload
that matches over here is fine
because this was never instantiated.
Now if you didn't have the if constexpr here,
you would be trying to call print
with zero arguments
when this parameter pack is empty,
and there's not an overload for that
so you get a compiler error.
But with if constexpr,
you don't get the compiler error,
you just never instantiate.
Syntax is this,
and at some points in time,
this has been called constexpr if
because the syntax used to be the other way around,
but that got complicated
because then you needed to have additional conxtexprs,
when you load else branches,
so it's a little unintuitive
if you read some of the early papers about this feature,
and some people still call it constexpr if,
but rather if constexpr.
So the condition must of course be a constexpr expression,
and the statements are discarded
if the branch is not taken.
So they can use variables that are declared but not defined,
and discarded templates,
discarded statements and templates
are not instantiated.
So, yeah so, we already talked about this.
So this can be used to replace SFINAE in a number of places,
so if you're writing make unique
before C++11,
you would need to use SFINAE
because you need to handle two cases.
One where the type T is constructable,
and you're going to use,
you're going to do new T parentheses,
the list of arguments.
But if it's not constructable,
you need to do brace init.
And so you just have
this SFINAE constructible, not constructible.
Now in C++17, you can just use if constexpr here,
and if it's constructible,
only this statement will be instantiated.
If it's not, then only this one will be instantiated.
And you can do some more advanced things with this,
like, if you're familiar with,
I'm sure advance from the standard library.
So that takes an iterator and advances it
some number of elements.
And usually this is implemented
with tag dispatching.
So there's different implementations that you'd want
based on, depending on the type of iterator
that you have,
and so you go and figure out what is the iterator category
of the iterator I've been given,
and then I'll go call this other function
which, with an instance of this iterator category type,
and overload resolution will point me
to the correct implementation.
In C++17, we can just use if constexpr here
to write this in one overload, and simplify the logic
so that you don't have to think about the overloading here,
you can just sort of follow the control flow.
If it's this type of iterator, you do this,
if it's this type of iterator, you do this,
et cetera, et cetera.
Yep.
(audience member asking question without microphone)
It's a little hard to see back there.
Gotcha, sorry.
Yeah, I will do my best on that,
unfortunately I have not checked whether,
how it was going to show up on this screen.
My apologies.
(audience member asking question without microphone)
Is there any way we can kill the lights?
(audience member asking question without microphone)
Okay, that would be great, yeah.
(audience member asking question without microphone)
Yep, they're on, the first slide had the link.
I can go over it, pull that up.
(audience laughing and chattering)
Hang on.
Okay, the link's right there.
I think that one's a different shade of blue.
So I'll leave that up for a moment,
let you guys grab that.
(audience member asking question without microphone)
Yeah, that would be awesome.
If you can just turn them off, that would be great.
Alright, sorry about that guys.
So we definitely have plenty of time,
so I can wait a couple minutes
to see if they can shut off the lights before moving on,
or I can just keep going.
Sort of up to you guys.
Keep going, you want?
Okay.
Let's see where we were.
It's getting there.
Okay.
Alright, so just refresher for where we were.
We're talking about if constexpr.
We just did this example with advance
showing how we could replace tag dispatching
with if constexpr.
So let's say that I have some sort of,
like person struct, like this,
where I've got three data members,
and then I've got three getter functions.
And suppose I wanted to implement a global get function
to non intrusively make this struct destructurable,
so that it would work with structured bindings.
So in C++11,
I could do this,
is this better colorwise?
Okay, awesome.
In C++11, I could do this
by having, you know, this is my,
well my get function's signature function's going to be,
declared here,
and then I could explicitly specialize it
for the three cases.
C++17 with if constexpr,
we can just write this once here.
You'll notice that there's no static assert or SFINAE
to check for the out of bounds case
where I is greater than two.
And that's because in that case,
a return type of void will be deduced here,
and a void ampersand, so returning void reference,
is going to be an error
because it will try to instantiate void,
and so that would not compile.
And so that sort of SFINAE's it out.
Alright. Next, yep.
(audience member asking question without microphone)
- Yes.
I believe yes, I believe so.
Yeah.
So next up, we're going to talk about fold expressions.
So in C++11,
if we wanted to write a variadic function like this,
like this auto sum here,
so it takes a bunch of arguments,
and then it adds all of them together.
We might write it like this,
where we, again, we're gonna have
a number of overloads,
we're gonna have sort of some base cases,
and we're gonna have a recursive case here.
In C++17, we can write this in just one case
using what's called a fold expression,
so it's a new way of working with parameter packs.
So I've got here, this, in parentheses
N-S plus dot, dot, dot, plus zero, close parentheses.
So fold expressions apply binary operators
to parameter packs.
There are four types of fold expressions.
They differ in the order of application,
and whether they take an explicit initial value.
So an unary right fold does not take an initial value,
and it applies from the right.
So it's, we would expand out to this.
Left fold would apply from the opposite direction.
And then the binary fold,
the same thing, except they take an initial value,
which is what's given if the parameter pack is empty.
So all binary operators are foldable,
so this is a list of all of the binary operators in C++.
So for this sum that I showed you guys before,
where we, what we have here is a binary right fold.
So, in parentheses N-S plus dot, dot, dot plus zero
plus parentheses,
and this would expand here if I did a for argument,
is it would be 3.14 plus 1e7 plus 42, negative 42,
plus 17, plus zero here,
which this is the initial value,
and if I gave it just nothing,
it would just return zero
because that's the initial value.
So a boolean and function would be a good example
of a unary left fold.
So this is going to fold over the boolean and operator,
so it'd just look like this.
Just dot, dot, dot, ampersand, ampersand,
and then the parameter pack.
And so I tell you the for arguments here,
would expand out to this.
So if I call it with no arguments here,
what does it expand out to?
Well, for these three operators,
if the parameter pack is,
they have sort of special semantics.
If the parameter pack is empty,
then the value of the fold is,
for boolean and, true,
for boolean or, false,
and for the comma operator,
a sort of void default construction expression.
And for any of the operators not listed above,
an unary fold expression with an empty parameter pack
is ill-formed.
So you can do some,
here's another little neat one.
This is a variadic print function here.
It's a binary left fold over the left shift operator
with an initial value of std cout.
See the initial value's right here, std cout.
Then the operator's right here,
dot, dot, dot, the operator again,
and then the parameter of, the expression
that gives us the parameter pack here.
And then this is not part of the fold right here.
And this for each arg here says
takes a function, and then a parameter pack of arguments,
and calls the function once on each argument,
and this is a unary right fold
over the comma operator,
and it takes advantage of this.
Whoops.
It takes advantage of that last special case right here.
Yep.
- [Audience Member] Actually two questions.
On the sum examples, zero is optional, correct?
- Zero is not optional
because we're summing over the plus operator,
and the special cases here,
so you need to provide the initial value
for operators that are not covered in the special cases here
or if the default initial value in these special cases
doesn't work for the types that you're,
that you're dealing with here.
- [Audience Member] Second question
for the previous example.
- Yep.
- [Audience Member] Does it print
all our values and then the line
or it prints valueless then the line greets you?
- That's a good question.
So, the fold expression here,
which is in parentheses,
so this is completely separate
from the new line.
So it's going to print all of these arguments,
and then one new line.
Not one of these arguments, new line,
one of these arguments, new line.
If you put the new line,
let's see where did you have to put it?
Yeah.
I think, yes. That would do it, yeah.
If you put it inside of the parentheses here,
then you'd get argument, new line,
argument, new line, argument, new line.
Nope.
(audience member asking question without microphone)
Ah, right.
Yeah, yeah, yeah, yeah.
Yeah that would not compile
because then the result of that expression
would not be a, yeah.
Yes. Neke is right,
you'd have to do some other tricks
to get that.
Yeah.
Any other questions?
Okay.
(audience member asking question without microphone)
Sorry a little bit louder.
- [Audience Member] Folding and, and, and or, or.
Do they short circuit?
- Yes, the question was,
do the folding and, and and or, or short circuit,
and yes they do.
- [Audience Member] Thank you.
- Okay.
Next up.
Before C++17, we've had function template deduction
to simplify the usage of function templates
allowing programmers to emit explicit template parameters
when they can be deduced from the function's arguments.
So, like, we have this std make tuple function,
which takes advantage of function template deduction
to allow you to create a tuple,
where you don't need to explicitly say hey,
it's a tuple of int and double.
It's just deduced from the arguments to the function.
This is not, we've not had this sort of deduction
for class templates,
until C++17.
So now, you can just write std tuple,
without any template parameters,
and then these constructor arguments here.
And then the types will be deduced
from the constructor arguments.
They can also be deduced in new expressions,
so it's probably pretty silly
to new a tuple, but
it would, if you do new std tuple,
and zero, zero here,
you'll get a tuple of int, int.
(audience member asking question without microphone)
No. You cannot partially specialize.
Class template deduction,
I think simplifies a lot of common patterns from modern C++
like resource acquisitions initialization,
so for mutex you can just write std lock guard.
You don't have to specify the mutex type.
That's kind of nice.
So the class template parameters can now be deduced
in declarations, function style cast expressions,
and new expressions.
And they're only performed
if no template arguments are provided,
and there's no partial specialization.
You can't provide one of them, like right here.
That's not allowed.
There's also a sort of later feature
called deduction guides.
Because class template deduction doesn't always work
as you may wish,
so this facility lets you control
how class template deduction operates.
Now these deduction guides don't have to be templates,
and I'll show you guys an example of this.
They are not defined inside of a class.
They're defined after the class definition,
and they have to be
within the same scope as the class template.
So, these are sort of best understood through examples,
I think.
So like this deduction guide is in the standard library.
So what this, and the syntax is right here,
so it's template name, so vector,
and then the parameters here,
and then a right arrow, and the type that this constructor
should deduce to.
So this is for vectors constructor
that takes two different iterators.
If we didn't have this deduction guide,
it might, it would try to deduce this
as a vector of iterators,
and it wouldn't know how to figure out
what the value type is,
of the iterators.
So what this guide says,
is hey if you're going to do iterators here,
this is how you go and find the
the correct value type from an iterator.
And so the vector's constructor that takes two iterators,
this one here would go and use a deduction guide;
whereas, doing a vector from a initializer list
would just use automatic deduction.
So as I said, they don't need to be templates,
so if I have this, like template T here.
Struct name, it's got a constructor
that takes a, like a first and last here.
I could have, oh,
there should be character const stars there.
I could have this deduction guide,
it's not a template,
which would point a expression like this
to this constructor.
Sorry there should be a second char const star there.
Okay, any questions on this one?
Deduction guides are sort of a more advanced feature.
They're a little bit, there's a number of caveats here
in how they work.
(audience member asking question without microphone)
That is, that is the sort of suggested usage, yes.
- [Audience Member] What was the question?
- The question was,
are deduction guides for the class
author of the class user,
and the answer is they're,
they should be used for the class author,
but there's nothing that necessarily stops the class user
from using it.
(audience member asking question without microphone)
Yes.
(audience member asking question without microphone)
Right, but I could go and add this
if this deduction guide wasn't in the standard ipo,
and add it.
The question was,
these are put into the same scope as the class,
so doesn't that prohibit users
from extending third party classes?
I don't think it necessarily does.
It is, if you want to go and do it,
perhaps you could abuse this feature.
Okay.
So next up, I want to talk about this feature,
auto non type template parameters.
Oo, that's one too far.
Nope, that was correct.
So std integral constant
is one of the fundamental meta programming primitives.
We use it to express compile time values.
Something like this.
Struct const where it takes two template parameters.
The first one is a type template parameter,
and the second one
is a non type template parameter of that type.
And then it just has a static constexpr data member
that has that value.
So you could use this to express a compile time integer,
compile time character, et cetera, et cetera.
So in C++17,
instead of having to have two parameters
and explicitly provide the type
of this non type template parameter,
we can write template auto,
and just provide the non, the literal argument,
like some literal integer here,
or character, or you know, boolean,
or in this case here, like a function pointer,
and it will deduce the type.
So this is pretty nice
because it simplifies expressing compile time constants.
You don't have to write things twice.
It's somewhat similar
to some of the other deduction features
I just talked about.
So this is also known by some people
as template auto.
It uses the regular auto deduction rules,
so you can sort of constrain it
in the same ways that you can constrain auto
in any other place.
So one thing this is useful for,
is that previously non type parameter packs
had to be homogeneous.
Like you could have this struct sequence,
where you say I want to,
you know, a list of compile time integers,
or a list of compile time characters,
but you couldn't say, like I want like a compile time tuple,
and now in C++17,
you can have something like that
by doing template auto dot, dot, dot,
so, like right here, this would be my tuple,
where I've got an integer literal,
or a character literal, and a boolean literal.
And then when you're dealing with these types,
you can use decl type
to figure out which type each element is.
So one place where this is useful,
some of you may be familiar with span
from the guidelines support library.
It's a non owning view of a contiguous sequence of objects,
so like a pointer plus a size conceptually.
The number of elements in a sequence
can either be provided at compile time,
by just specifying literal,
or at run time by specifying the template parameter,
this sort of magic value dyn,
which, in the sort of C++11 to 14 era span
would just be a constexpr global variable
that has some magic value.
That's of type std size T,
but is some value that presumably nobody would ever provide.
Like negative one.
And in C++17, instead of having this magic value,
that's you know, actually a std size T,
we can create some trivial type here,
and use it as a tag type,
and use template auto instead of std size T
as the second parameter here.
And then in this second case,
when we write dyn here,
we can use decl type inside of span to distinguish
between this tag type, that indicates something,
in this case that the length of the span will be provided
at run time, via constructor to span.
Or the std size T compile time literal.
Speaking of tag types.
C++17 addresses one of the major pain points
with meta programming.
C++17 chooses to inline variables.
So, now, like I can mark this global constexpr,
this global variable as inline,
and it's actually implicit for constexpr data members,
but it is required here.
And then I can use this library
in multiple translation units without having to
deal with odr violations.
So variables can now be inline just like functions.
They may be defined in more than one translation unit
as long as the definitions are identical.
The definitions must be present in a translation unit
that accesses the inline variable.
And these inline variables have external linkage,
so they're not static,
and they must be declared inline in every translation unit,
and they have the same address in every translation unit.
And a static constexpr member variable
is implicitly inline.
So, like this is quite nice.
Like before, if I wanted to have some global atomic bool
as a flag to indicate that something's ready
within some toy project I'm working on,
I would need to have it in the header,
and have it extern,
and then in the source file initialize it,
but now you can just write inline std atomic bool
ready, initialize it,
and put that all in the header,
and that's fine.
I mean, you use multiple translations.
And also, we could stick this inside of a struct,
and make it static and still be fine,
just mark it inline.
Oh, that was the last one there.
Alright, next up,
I'm going to talk about two different features
relating to using lambda in constexpr context.
So before C++17,
it's been a little difficult to use lambdas
in constexpr context.
So if I just write a lambda like this add lambda here,
and I try to call it in this constexpr expression here,
to construct this int I,
I'll get a compile error because the call operator
of the synthesized lambda is not constexpr.
In C++17, we don't have this restriction.
You can, there's now a syntax
where you can indicate that the lambda
should be constexpr,
but it's actually been made implicit,
so you can write this now,
but you don't need to.
This will just work out of the box.
And the reason here,
is if the compiler can just figure it out
because it has the definition available
because the lambda's written right where it's used.
It's not, never going to be forward declared
and then used somewhere else.
It's always going to be visible.
You --
(audience member asking question without microphone)
well, but how would you declare a lambda constexpr?
(audience member speaking without microphone)
Oh, yes. You would get an error.
If you write this, and it's not actually constexpr,
you would get an error.
If you did something that's not legal
in a constexpr context inside this lambda,
you would get an error;
whereas, if you wrote this,
and you did something here that you couldn't do in constexpr
you would only get an error here
when you tried to use it in the constexpr context.
Yes?
- [Audience Member] What is the type
of the lambda itself?
When you mark the constexpr,
does it change the actual function object?
- The question was,
does it change the actual function object?
Can you clarify in what way?
- [Audience Member] An object that has a callable operator,
and isn't a name for callable operator itself,
a constexpr function?
- Yes. So all it does is that
the synthesized call operator of the lambda
is now synthesized if it was marked constexpr,
as if you wrote a function object
with the constexpr call operator.
Let's see, you can also create constexpr lambdas.
So here this is a lambda with a constexpr call operator,
but this is a constexpr lambda.
The distinction here, is that this lambda you can call it,
it's call operator is marked constexpr,
but its copy constructor is not.
Its constructors are not.
Whereas this one, is being,
does have a constexpr constructor and copy constructor.
And this is of course relevant
if you have captures
and your constructors and copy constructors
and assignment operators are not trivial.
So Louis Dionne has an interesting use case for this,
which is that he can use it as a way to implement
an efficient tuple,
where, sort of, you do it recursively,
and use captures.
He gave a talk at C++ Now, I believe,
C++ Now 2014 about how to do this.
Alright.
So before C++17,
static assert required you to give,
to give it two arguments.
The first of the expression,
and in the second, a failure message.
Unlike assert, which just requires one.
So a lot of people like me got into the habit
of often writing in static assert,
something like is addable V T,
and then just an empty string,
because we didn't want to think up
a particular assertion message.
We just wanted to get the assert fired.
If we made a mistake,
and then it wasn't really something that we were using,
that we were expecting users to run into,
it was just something internal,
we didn't really want to provide a message.
So now, static assert has two forms.
It can either take a message, or it can not take a message.
So this is a small feature,
but it means you don't have to explain to your coworkers
why you've written this.
So also in C++17,
return value optimization,
which is performed by a very C++ compiler, more or less,
wasn't required before 17,
and now it is required.
So previous, prior to C++17, you could not write
something like this grab lock function here
where you do, where it takes a std mutex,
and then it constructs this temporary lock guard
and returns it.
The reason you couldn't write this is because
even though it's very clearly going to be rvo'd,
you'd get a compiler error
because the copy or move constructor would be required
and lock guard has neither.
So in C++17, rvo is now guaranteed,
so the compiler does not enforce
that you have these constructors
that would never be needed.
So this is nice for writing this sort of factory function,
where you have a type that is not copy or move constructible
but it is somewhat of a small feature.
Now one thing to note is that only rvo is mandatory
nrvo, so named return value optimization is not.
So it's only this case,
not the case where it's a named variable,
like if it was one of the arguments here
that you were going to return.
Anything that's nrvo is not guaranteed,
and would still have these requirements.
Alright.
So this is the,
we've got one or two more,
so we'll probably break a little bit early.
In C++17, you can now declare nested namespaces,
so instead of doing Namespace A, B, C,
you can just do namespace a, colon, colon, B, colon, colon C
so again, this is just a little syntax hack.
And then, we've got this new pre-processor predicate
called underscore, underscore has include,
and this can be used to test if a header include exists.
So like right here, I'm doing pound if, has include
so if I have a string view available,
then go ahead and include it,
and otherwise, if I have experimental string view available
include it,
and otherwise, just define
this have string view to zero here.
And so what this does is it just like,
it searches the header,
it searches for the header
but does not include, does not actually include it
into the file,
so it allows you to test for whether a header exists.
Pretty straightforward.
Okay, so, yep.
(audience member asking question without microphone)
So the question was,
how does this work with different versions of C++?
So the example you gave was that
if I was compiling, trying to use string view,
but using C++14.
This is a good question.
I am not certain whether this is something
that, that would be backported to compilers.
I mean, obviously if you're using a compiler
that's older than this feature,
it probably is not going to be around.
(audience member asking question without microphone)
So the question was, if C++20 introduced a header,
that was not in C++17,
would this feature find it
(audience member asking question without microphone)
when compiling in C++17.
I suspect that's probably.
I'm not sure I have an answer for that.
Let me get back to you after the break.
I'd have to look that up.
I am not sure.
(audience member asking question without microphone)
My guess is that it's probably implementation defined.
Or --
(audience member asking question without microphone)
You can't say if def, because this is not,
this has include is not a pre-processor definition.
It's like this pre-processor,
it's a pre-processor predicate,
so it needs to be pound if,
not if def.
(audience member asking question without microphone)
Right, okay, so Mihar pointed out
so CPP reference indicates,
it only checks whether you can include a file,
so like whether the file is there.
(audience member asking question without microphone)
So, I think part of the answer here
is that the standard does not really have a notion
of different versions of the standard.
There's just sort of the latest standard,
and I'm not certain whether a modern compiler
with a std C++11 flag
would make this feature available in that mode.
Because it's not part of C++11.
So you might just get a compiler error
because it doesn't know what has include is.
Alright.
So that was the last of the language features.
We've got 10 minutes until break.
I think we'll wait until,
we'll start on library features after break
if anybody has any questions.
Yep.
- [Audience Member] So the has include
is not checking if this file has already been included
in this compile time?
- No it is --
- [Audience Member] But it could be included.
- It is, yes.
It is testing for whether you can include the header, yeah.
Okay.
Any other questions
on this first half of the talk?
Yep.
(audience member asking question without microphone)
The question was is there other forms of this
to test for functions or symbols, or?
Not right now, no.
It's just this one particular one, has include,
and you wouldn't be able to do that
in the pre-processor anyways
because that would involve parsing.
Alright, I will see y'all after break.