- 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.
And “C++17 Features (part 2 of 2)”