C++ Club - 150. Concepts, UB, interview questions, Boost
Episode Date: July 5, 2022I'm joined by Bjarne Stroustrup, Gianluca Delfino, and other colleagues.Video: https://youtu.be/KWcSk1hsZEUNotes: https://cppclub.uk/meetings/2022/150/New episode numbering!...
Transcript
Discussion (0)
Okay, let me share my screen.
Right. Okay, so welcome to the meeting number 150.
We'll start with the C++ podcast news.
C++ cast is on indefinite hiatus.
It's a sad day for C++ podcast listeners.
Rob Irving, the creator of CppCast, which was the first podcast for C++ developers,
decided to hit pause for an indefinite amount of time.
His new job is.NET, which means he's not using C++,
and he can't justify spending time on it anymore,
because it's quite time-consuming.
And it's completely understandable.
Everyone is grateful, Reddit is full of praise.
One quote is,
Great podcast. I'm a bit sad they're invoking the CppCast destructor.
I'll always remember this as my favorite programming podcast.
End quote.
CppCast had a good run, with Robin, his co-host, Jason Turner,
producing an impressive total of 349 episodes over the years.
All high-quality content with many interesting guests. I mentioned CppCast a few times in our meetings, and I still have
a long backlog to listen to, so it's likely that I will continue mentioning it for some
time. All the best to Rob and Jason in anything that comes next.
Godbolt Compiler Explorer is 10 years old, can you believe it? 10 years ago, Matt Godbolt open sourced a web tool he built, Compiler Explorer, which used to be called
GCC Explorer. It turned into an indispensable tool for visualizing the work of compilers and code sharing.
Most people probably just call it Godbolt now.
I do.
You not only can use it on its original website,
you can also self-host it in case you have additional security requirements
or cannot share proprietary code online.
A quote from his article. Ten years ago I got permission to open-source a little tool called
GCC Explorer. I developed it over a week or so of spare time at my then employer in Node.js and the rest, as they say, is history. And this is how it looked back then. Much simpler
and much less capable than it is now. In his article Matt mentions people who supported him
and developed Compiler Explorer together and shared some ideas for the future,
like supporting more CPUs and architectures, user accounts, better support for GPUs,
and actual support for mobile devices. Right, speaking of online tools, there is another one called QuickBench.
It's a C++ micro benchmarking tool that allows you to compare performance of several code
snippets using Google Benchmark. It draws nice charts, as you can see, and can also pass your code
onto one of the publicly available C++ compilers, including
Godbolt and C++ Insights.
And speaking of C++ Insights, it's a tool by Andreas Fertig
that shows you an intermediate result of compilation of a snippet of code,
so that you can see what actually happens in the compiler.
The result is still C++.
It's not underrated. It's always interesting to see what the compiler actually
does, but not many people actually use this. Definitely. It's like a glimpse into the soul
of the compiler, at least the initial stages. Some of the things you don't want to see, but you better see. Yeah.
The code is transformed to illustrate how the compiler sees your code.
Right. Next, C++ standard stHive update.
The long-awaited proposal, which had
19 revisions by now,
was discussed again
in the committee telecom.
And you will not believe what the
next steps are.
And the next
steps are, come back with revised paper addressing feedback.
How many more revisions will it take, I wonder?
It took, I think, 26 for concepts.
Oh right, so that's not the longest.
No, no, no, that was contracts and then it died.
Right, well it's sort of going slowly, so it could beat this record.
So this Redditor asks,
do C++20 concepts change compile time positively or not?
On one hand, a C++ concept adds more stuff to digest for the compiler. On another hand, it imposes constraints, hence reducing Sphene stages.
Do you have some feedback about this?
And one of the replies says,
Very positively, they nearly eliminate the need for Sphenae.
And the code is much cleaner.
Instead of weird template parameters, you can have clean requirements listed.
Jonathan Wakely replies.
He's a GCC developer working at Red Hat. He says that GCC has made a significant progress in concepts compilation efficiency lately. He also notes that
concepts can be used for member function selection from the overload set and are better at it than SFINAE.
Concepts allow the constraints to be expressed in a simple form that takes less work to compile and that can be optimized internally by the compiler more easily than SFINAE hacks.
Additionally, subsumption and partial ordering of constraints means that you don't need to repeat the constraints
on every overload. For example, if the enableIf constraint above in this code snippet
if the enableIf was used on one member of an overload set, you'd also need to negate that constraint
on the other members, which you don't need for concepts.
Yep, very nice and expected and predicted and it's very nice that somebody
puts this information out publicly. Well, yes. It simplifies a lot of code.
C++20 features are becoming more available to people
who are switching to newer compilers.
And so more and more articles of that sort are starting to appear.
Tristan Brindle raises this interesting point. Quote, just to add to this,
another benefit of concepts is that they can be used in places where Sphenae can't, like on
destructors and copy-move constructors. This is useful for things like stdoptional, whose special
members should be trivial whenever the equivalent operation on T is trivial.
His example code snippet is a partial declaration of optional class template that has two destructors,
one defaulted for when the contained type is trivially destructible with a requires
clause after the parentheses that uses std is triviallyDestructible and another user-defined one for when the contain type is not
truly destructible, which has a body that calls the contain
type destructor.
This leads nicely to the next topic.
Rainer Grimm posted an article on his blog about using requires outside concepts.
I know it's not the ideal usage, more of an ad hoc usage, but still as a way to get more people to use concepts, I guess that's allowed.
As you can see here, you can use requires in static assert.
This function returns
checks that a class has a way to return the count of objects,
and that count is convertible to int.
You can also use requires in constexprif.
This is a function that can return the number of elements in a container
or a custom class, and one of those classes can use a function
that differs in name from another one, like count and size.
And if you use the requires in if constexpr,
you can support both in one function.
It seems to be a very good use of requires. I've used this myself and you can use it in const evaluated contexts as well and you know it does save a lot of template instantiations and complications in the code.
It looks very good to me. In many cases, it's a bit of a hack because it doesn't document what
you really are doing, it just says how you're doing it. In other words, if you are doing
something principle, the likelihood is that it actually should be a concept and have a name.
Definitely, yes. You know, just not the requires, but the concept in the if statement.
Exactly. I certainly use concepts for that, both in static assert and in constexpr. But I'm leery of code, even if I write it, that uses, requires all over the place, because
it's like putting the contents of a function inside something, and you don't really know
what's meant.
You know what's meant. You know what's done.
Yeah maybe in this case it could be viewed as a as a first step towards
extracting these into a concept. Yes that's one way. I've certainly done exactly that. I wrote something, and thought,
oh god, this is a hack. This should have a name. I might be able to use it elsewhere.
And I mean, a concept is a function, as you see here.
Another interesting use case is to put a constraint on a non-type template parameter.
Let's see if I can find that example.
Oh yeah, if you have a say an int non-type template parameter, you can add a requires clause to limit values to particular
range. Rayner also describes the requires requires thing or anonymous concepts. This requires requires,
if you remember, attracted attention and undeserved ridicule
of concept skeptics back when they were introduced.
I know I remember at least one presentation when this was laughed at.
Reiner says, you can define an anonymous concept and directly use it.
In general, you should not do it.
Anonymous concepts make your code hard to read and you cannot reuse them.
So the first requires in this is the requires clause and the second defines a requires expression.
And as usual you shouldn't try too hard to make ugly things pretty.
And this stuff is very often ugly. Yeah.
So a very interesting article and I hope to get more into concepts as I gain more experience using C++20.
Next, we have this article by Mohit Saini.
Shocking examples of undefined behavior.
They are not that shocking if you already know about UB, but if it's your first encounter,
boy are you in for a surprise.
The first example of UB by Eric Musser originally is signed into Jira Overflow combined with
optimizer.
And there is a Godbolt link for that.
Initially it wasn't supposed to be an endless loop, but as you can see here
it's not an endless loop when it's compiled without optimizations, but as
soon as you try to optimize it it turns into an infinite
loop. How does that happen?
It's very weird. So the initial code is a loop that loops from 0 to 9. And there is an output statement that outputs a result of
multiplication of the loop variable to a very large integer.
So what the compiler does in its first step of optimization is a bit
unintuitive, but if you stare at it long enough, it kind of starts making sense.
The compiler is... sort of hoists the calculation one level up to the loop condition and increment.
And so in the cout statement is just the loop variable.
Now the compiler assumes that signed integer overflow
never happens in a valid program because it's UB.
And so the next step, the compiler reduces that multiplication to true.
Because...
Didn't the compiler just introduce
UB?
Well, the loop
would have resulted in UB
anyway.
I think that's
the idea of the original
author.
So the
J times that large constant is u b and the compiler is allowed
to do anything. Yeah. So after that, the compiler thinks, oh, so the right hand of this comparison is larger than int max, which cannot happen. So let's just turn
the whole condition to true. And so the loop becomes infinite. Very weird. The second example
I've seen before, it was originally shared by Krista Wolfritzen, and it's about dereferencing a null pointer.
Let's look at it in detail. Let me try and show you the godbolt for it.
So what happens here?
The function neverCalled is never called explicitly in this code snippet.
Nevertheless, as the result of the compilation, the function eraseAll is called, which is
surprising and potentially disastrous. If this wasn't commented out,
you'd probably end up losing your file system, provided you're the sudo user.
So let's see what the compiler sees in this. Look at the function do that is defined here.
It's static, so that means it can only be modified by this translation unit and nothing else.
In this translation unit, the only possible values for do are nullpetter and erasal.
Since do gets called in main, it cannot be nullpetter, as it would be ub, and we can't
have that in a well-formed program.
That means that do can only only initialized with eraseAll.
And as a nice side effect, let's just turn neverCalled into a noop.
And so, it happens that main just calls eraseAll.
Boom.
Ah, compilers. A Redditor suggests a solution for the first case. Well, kind of solution,
or mitigation, I'd say. There is a flag that you can use in GCC and Clang, which is fwrapv, and that makes signed integer
overflow defined by telling the compiler to assume that signed integers wrap using
two-complement arithmetic on overflow.
Another editor attempted to suggest using unsigned integers with defined overflow semantics,
to which there was a reply.
The problem is, they have very unhelpful behavior a lot of the time, and are widely considered
a mistake in the standard library.
Glad we got that solved, and we'll never need to revisit it again ever.
As ever, there is someone telling us that Rust does it better.
And I like a quote in the thread which goes like this.
The UB comes first, the unexpected results follow.
Library. A Ukrainian developer living in Kyiv, Daniil Goncharov, developed a utility library NameOf for obtaining a string representation of pretty much any C++ object at compile time,
be it a variable, a member variable, a type, or even a macro. It's a single header file
with lots of magic, and it can be a great help for logging and debugging.
The library comes under MIT license, supports Windows, Linux, and macOS, and can be installed
using a VC package or just by incorporating the header in your project.
Much of the functionality uses a compiler-specific hack based on the macros pretty-function and
func-sig, which work on Clang, msvc, and gcc.
There is also discussion on Reddit, and while browsing Daniil's code,
consider supporting Ukraine in the fight against Russian invaders.
xMake. 266 has been released. xMake is a very capable cross-platform build utility
and package manager based on Lua. Strong pre-make vibes here, but it's much better.
It supports other package managers like Conan and VC package out of the box.
It also supports modern C++ features like modules.
From what I can gather, it supports them better than other build systems.
It can generate project files for Visual Studio, CMake, and Xcode.
It comes with sensible defaults and is generally very pleasant to use.
In this version is support for NVIDIA HPC SDK compilers for C, C++, and Fortran. And support for distributed compilation, which is cross-platform, works with MSVC, Clang,
and GCC, builds Android, iOS, Linux, Windows, and macOS programs, has no dependencies other
than the compilation toolchain, supports load balancing and scheduling, supports real-time large file compression transfer using
LZ4 algorithm, and has almost zero configuration cost. There's no shared file system required.
Sounds pretty cool. Give XMake a go for your next pet project.
I did and so far I'm pretty happy. You're not looking back to CMake?
Oh no, god no. Not for my pet projects. Life's too short.
Right, C++ interview questions. A Redditor asks, what are some C++-related questions
that you have been asked in a job interview?
There are various questions
of various difficulty levels
from beginner to advanced.
And I will quote a sampling of replies from the thread.
What is polymorphism?
Make a polymorphic container.
What are smart pointers?
Prove you can use them.
Explain virtual tables.
How do you allocate memory in C++?
How does it differ from C?
Well, for a C++ role, I'd say that difference from C is maybe not critically important, but still useful.
How do you allocate... oh, sorry. In Java, they got try, catch, and finally.
The latter performs some code, no matter whether an exception was thrown or not.
We've got no finally in C++. How do we achieve the same effect nonetheless?
For this one, array.ai is the best answer.
And you can also use some utilities like GSL finally,
I think.
Exactly.
I mean, it's 10 lines of code.
It's still based on arrayII, but if your classes don't support that
directly, then you can use that.
And you hope that we're going to ever get finally standardized?
Sort of.
This is a standards committee, so it becomes much more complicated.
It's not just final.
It's like finally, but you can say that it's only recalled when it's an exception or only recalls when it isn't an exception.
And you can turn it on and off.
This is supposedly an improvement.
I like the simple finally.
I also like the simple final.
I also like the simple one.
I don't know what was wrong with it.
It doesn't do anything.
It doesn't do everything that somebody could imagine.
It doesn't do perfection.
Right.
And of course, it also means that you can see one of those,
and you don't really know what's going on.
Because in the code, after you've set the final reaction,
somebody might turn it off.
Yeah, that's not ideal when you're relying on it.
I strongly prefer the simple.
Can't do interesting.
Yeah.
I think Boost also has.
It's like my argument against GoTo.
It can do everything.
That's why we don't want it.
Yeah.
I think Boost also has one if you don't have access to GSL,
like for an older code.
I think GSL requires C++14 now.
Microsoft's GSL.
Another question, or rather quote
I had my last C++ interview
three years ago and the references
weren't mentioned at all
then again I'm in the game industry which
is notoriously resistant to modern
C++
I wouldn't say references were more than
C++ it's like C++
but yeah game industry I wouldn't say references were more than C++. It's like C++.
But yeah, game industry.
1983?
I think in Doom and subsequent engines,
the references were banned.
I think, I don't know until when,
but probably until recently, they were still sticking to pointers
only. So it
was C with classes
kind of thing.
And references were already
kind of forbidden
luxury.
Yeah, there's a whole
repository of guidelines called Orthodox C++.
It's like anti-modern C++, but it's a very hard reading.
Another question. You need to push one million elements to the container using pushback method. Which container
will you choose? StdVector or stdList and why? And the following follow-up question was how many
memory allocations will happen if you use pushback in vector to push 1 million elements?
And the answer is the number of allocations depends on implementation but will often be log2 of the 1 million elements, because
by default most often vector allocates twice the capacity each time it runs out of space.
Of course a better solution is preallocate. allocate.
Another one, what's the difference between MakeShared and SharedPointerConstructor?
That's related to the control block which is allocated in one block and two blocks if
you don't use MakeShared.
This is the kind of question that people ask when they just found out about this, and they think, ah, this is something that is a good thing to know, and I want to ask.
I know about it.
Let's see.
My experience is that students coming out of university doesn't know this on average.
That's not the kind of stuff they're taught.
You can argue that many questions in interviews are just details and they are
not essential but just a way to demonstrate the interviewer's proficiency
and superiority. Kind of a show off, yes. A bit of a dusty corner here and there that you found out and you remember.
Some other questions were, what's the difference between a struct and a union?
What is virtual inheritance? What is a union used for? Rule of five, rule of three, RIII, what do they
mean? What's the difference between a reference and a pointer?
What does the keyword volatile mean?
What are the different types of cast operations?
What does the inline keyword do?
Explain slicing.
Explain how Sphenae works in pre-C++20.
Explain what a cross-thread data race is, and if a class has a destructor,
it should probably also have a blank and a blank, and maybe another two blanks. What are they?
That's the rule of five, which is one of the more useful questions, I'd say.
There were a couple of code snippets in the thread. Let me find them.
The first one was, what's wrong with the following class declaration?
Oh yeah, this one. This class string.
What's wrong with it?
And the answer was that since the class member variables are initialized in declaration order, string star will be initialized before length is initialized.
And since length is uninitialized, it contains garbage, and accessing it is ubi.
Even if you flip that around, the code is pretty bad anyway.
Well, it's like a one-issue demo code. Don't pay attention to anything else.
But I guess if the person starts telling you what else is wrong with the code, it's a good sign.
My question is, how many compilers catch that one?
The out of order initialization, I think, with all the warnings on, probably many.
I think GCC and Clang will catch it without any... with the all...
W-all? W-all? No, hang on.
I'm not sure. W-extra? Something? Yeah, one of those. They will catch it. But I don't think MSVC will. And if you have ReSharper installed, it will sort of suggest that it's a problem.
So this is another code snippet. What will the following program output and why?
And the correct answer here I think is A, Fo, because calling virtual functions from a
constructor makes them behave like non-virtual functions and is generally
not a good idea to avoid confusion.
I remember reading a C++ hiring advice some time ago. Ask the candidate a
complex template metaprogramming question, and if they answer correctly, don't hire them.
Good one.
Redditor asks, is Boost still relevant?
Is learning Boost still essential with C++20?
And one of the answers that I thought was sensible was like this, actually the first
one, quote, I'm not sure learning Boost is the right term, maybe understanding that it
exists and take advantage of it if necessary. What I tell my new developers is that
if you need a container or algorithm
or something that is generic and not specific to our business,
is to first check the STL and core C++ libraries,
then boost, and if it is not there,
look for another open-source solution.
If you can't find something that fits what we need,
code it yourself or modify one of the existing implementations if that would make more sense.
Boost incubates a lot of code before it goes into the C++ standard,
so often has things you can't find in C++20."
Some of the Boost libraries that could be still useful
and were named in the thread were
Variant2, a variant which always has a value,
ASIO, also available as a non-Boost library,
BEAST, which is networking and web sockets built on top of Boost,
ASIO,
MP11 and HANA for metaprogramming,
MP11, I think, only if you don't mind long compile times,
Describe for reflection,
JSON parser,
a multi-precision library,
and Regex, which is much faster than std regex.
To an extent also the small library to interpret the command line parameters, what's it called?
Oh yeah, I think it's like boost command line or something like that,
or boost parameters, I don't remember, but yeah, I know what you're talking about.
Which is a weird syntax to be on X.
Yeah. I think it also has a nice feature where you can define parameters using environment variables,
which not all command line interpreter libraries do.
There was another great answer from the thread, quote,
there is no single thing from Boost that I have a need for in every project, but I nearly always
use something. And that poster listed something they used, which was Boost algorithm for various
string-related algorithms like trim, split, etc.
But some of these are obsolete with the addition of ranges.
Specialized containers like flatMap, staticVector and stableVector.
Boost lexical cast, because parsing arbitrary numeric types in C++ is still absurdly difficult. Boost process, Boost format, although from now on they will only be using FMT or STED
format, and Boost hash.
They continue.
Has Boost gotten a solution to the problem that you get all of Boost if you want anything?
I think that's still true. Those libraries that are not header-only and produce libraries, they are separate, so you don't necessarily link to everything you need.
But Boost is being imported as one thing.
So Boost version.
And so the problem is you bring Boost into a project
to get something specific, and then you wait a year,
and then all kinds of strange features has been used. And you can't see it unless you look very careful
yes yes there is a danger of boost creep oh yes
but um yeah i don't know how to fight that. Code reviews, probably.
Okay, so next one is,
Pure Virtual C++ recordings are available.
Cybrand writes,
Pure Virtual C++, a free one-day virtual conference
for the whole C++ community, run in April,
and all sessions are now available to watch online.
The titles sound quite interesting.
There were pre-conference sessions on MSVC C++ 2023 update by Stefan T. Lauerwaid.
Live session, What's New in C++ 23 by Sybrand. A session by Gabriel Dos Reis, persistent representation
of C++ for fun and profit.
I'm guessing it's related to modules. No, not really.
That's the representation that's a development of the IPR, which is, I think, called IFC when it's a really interesting way of having your program represented as a graph and then walking
the word and what is new is you can store this one and bring it back again there was a CVPcon 2021 talk by Gabby about that also.
It's being open sourced to use it.
But it's basically the old idea of representing a C++
program as a graph.
So it's one of the fastest graphs you can traverse.
So the fact that it's used in modules is just a small, one small part of its usefulness, I guess. um the design the original design of modules were based on the ideas of how you could represent and
use a program based on ipr the fact that gabby was deeply involved in the ipr has been the main person in the development of it into the bottom portion, and that he
was the one that did a lot of the modules is no accident at all.
Right.
Some of the other sessions were Stay Calm and Stress-Free by using a package manager, an overview of VC package,
targeting macOS from Visual Studio, dependent breakpoints, data breakpoints in Visual Studio
Code, hot reload for C++, Clang tidy in Visual Studio Code, productivity in Visual Studio Qt C++ tricks. So yeah, a good set of videos that I'll have to watch at some point.
I watched two of the short ones that Clang tied in Visual Studio Code. It was really nice. It's
just like 10 minutes. It just tells you how to set it up and a quick demo. Very cool. And I think one
of the two ones are about breakpoints and if I
recall correctly it was about possibly encoding a breakpoint just writing some sort of attributes
which I understand if I understand correctly is a feature that is also being discussed for
standardization although somewhat controversial for technical reasons.
You just can say in the code here is a breakpoint and instead of just clicking on the line,
you can just write break here and if it's running under the bugger then it's going to break.
I think that feature is already available in compilers
using compiler-specific functions like debug breakpoint that breaks hard.
I know that for sure in MSVC there is some compiler-specific one,
but if I recall correctly, there is a limit to how many you can use?
Like there is a limit of how many you can put, maybe?
I think the limit applies to the data breakpoints because they use specific CPU registers so they can only be like four maximum at the
same time it will make sense yes but breakpoints can be scattered all over
the place I think the proposal was to introduce std breakpoint and std isDebuggerPresent
two functions, maybe the third one break if under debugger or something like that.
So maybe you will get them in 26. I don't know, it's definitely interesting. I don't know if I
would use it, but it sounds like something that could be there. I don't know if I would use it, but it sounds like something that could be there.
I don't know.
I think the main problem is that then you have to admit that a debugger existed.
Of course it does, but now you have to say what it is in some sense.
And it's very hard to say.
This is a break point for the debugger.
The debugger is something that is not part of the standard
all right i see
so somebody's going to say what does that mean
i guess it's a similar problem with standardizing pragma once which suddenly introduces a concept of file system into the
standard and and and the fact that once doesn't actually mean once if there's a change in in the
in the file system or if something appears in one uncoordinated ways
pragma once is extremely useful unless you do something weak.
If you have some links, probably that maybe you
have some soft links to your files somewhere else,
then maybe Pragma once could get confused or something.
There's two or three ways that you can confuse pragma once. And, well, go and get
modules to work and have that problem anymore. Yes, please. Yeah.
Okay, next, we have another library, which is called Libassert. We've all used assertions
with some degree of success and in many cases we want to be able to do that at
runtime as well as during debugging, so in release builds as well.
So Jeremy Rifkin wrote a super advanced assertion library.
In his own words, it's the most over-engineered and overpowered C++ assertion library.
When an assertion fails, not only you get a message, a stack trace,
and local variable values, but these are all syntax highlighted.
Can you imagine that? This is how a failed assertion looks.
I'd say that's pretty advanced. I've never seen anything like it.
Can we see the assembly that this thing generates?
Uh, no. It's probably too horrible.
Does it spell check the messages while it's added?
That would be too much.
Supported constructs are debug assertAssert, which is similar to Assert.
Simple Assert, which is checked in Debug, but still evaluated and returned in Release.
Assume, which checks core assumptions, preconditions, and post-conditions in Debug, and provides
hints to the optimize and release.
Sort of similar to concepts, maybe?
Contracts?
Contracts, sorry.
Yes, of course.
And verify, which checks the condition
in both debug and release.
GSL also has similar macros, right?
I don't know.
Yeah, but they are not great we need something better
so contracts are definitely not going uh in 23 but are they going in 26 i don't know
i don't know what they are nobody knows at this point that right, they still seem to be mutating.
They haven't been brought to the committee as a whole.
My impression is that they serve part of the needs that people have, and not all. One problem I have is they seem to be going towards termination as the only possible way of responding to a runtime detection.
And that means that there's a fair amount of code that can't use it so if you imagine using a contract in standard library then could not have it
run except in debug
because you couldn't take termination take to cases
anyway
this will come up and it will come up
maybe next year
in the year
when
something comes out of
static
right
so
that was the last thing for today and I'll leave you with this tweet that pokes
fun at JavaScript, as you do.
JavaScript be like, equals equals, the same, equals equals equals, really the same, four
equal signs, really actually the same, five equal signs, really actually the same.
Five equal signs, you won't even believe how the same those things are.
And the last thing is a quote by Fred Brooks who wrote the mythical Man-Month, who says
what one programmer can do in one month, two programmers can do in two months.
And on that note, thank you very much, everyone, for coming,
and I'll talk to you next time.