Hello.
And welcome to C++ Weekly.
I'm your host, Jason Turner.
I'm available for code
reviews, contracting,
and on-site training.
In this episode, I'm going to
discuss the standard to_address
utility that is
being added in C++20.
And you can see it here.
It is standard to_address.
What it does is it gives you
the address of an underlying
object, basically returning
an actual C-style pointer
to it, even in the face of
being passed a smart pointer.
So let's go ahead and
play with this briefly
and see what we
can find with it.
Now, of course, compiler support
for C++20 features is currently
lacking.
But we do have some
ability here with GCC 8.2
and with Clang, at
least, to play with this.
And we're going to
do that right now.
So we are just calling
make_unique on an integer,
creating it with the value 15,
and passing this into this int
pointer object here.
And we can see our new and
delete being generated.
Now, let's say we're working
in some highly generic code
for some reason, and we just
want the address of this thing.
We want some way
of being actually
able to refer to a pointer.
Now, it's true that
there's not a whole lot
of use cases for this in
general, everyday code.
But sometimes we
are writing code
where we're handling something
in a wide variety of contexts
in a library and
we're past something,
and we just need
to know, well, what
is the underlying object here?
So I'm going to do this
in the form of a lambda.
No captures.
And I'm doing it so
that I can actually
have an auto parameter here.
This is so much shorter than
actually writing a template.
Now, as usual, I am using
Compiler Explorer here.
But I am also using my
own custom instance of it
that lets me actually execute
the code that I'm typing in.
So I'm passing in this int
pointer into this lambda,
and I expect that it's going
to write out the address here.
Now, I don't currently
have my system configured
to be able to use
our lib format,
in case you're
wondering about that.
This episode is, in fact,
recorded after the one where
I talk about lib format.
But let's go ahead
and compile this
and execute it and
see what output we're
getting from the compiler.
So we can see this value
right here is being printed.
And it's just some
random memory address.
And this is with
a unique pointer.
So I'm going to go ahead and
play around with this some more
and see what kinds of things
we can do a to_address on.
Now, I really don't
expect this to work,
because to_address is
expecting a pointer-like thing,
and I'm passing in a
plain integer here.
So I'm expecting
a compile error.
And in fact, I am
getting one here.
And it's quite easy to
read, as you can see.
Something I often tell
people is if you are getting
this kind of an error
back from the compiler
and you're immediately
overwhelmed, what
you need to do is start with
the very first line that
says there's an error on it.
And we've got this
static_assert, element_type
value.
Static assertion fails.
Pointer type
defines element_type
or is like SomePointer.
Yeah, that's not
terribly useful yet.
So let's get to our source code.
No match for operator,
arrow, arrow for to_address.
Operator, less than,
less than, that is.
So it's kind of telling
us something here.
Basically, we can see that we're
passing something to to_address
that it doesn't like.
But I can give it a
pointer-like thing,
and it should be able to
work there and also tell us
a pointer value here
to print out yes.
And now we have two
different pointer values.
And we can notice something
else interesting here,
if you haven't
spent a lot of time
looking at this, is the two
different address ranges.
That's something that's
created on a heap,
versus something that's
created on the stack.
So this is a stack variable.
And we can see it is
a much higher value,
because it is starting at the
top and working its way down.
And this is a heap variable.
And it's a much smaller
number, starting at the bottom
and working its way up,
for lack of a better
way of putting that.
And we can actually try to
see if we can observe that
a little bit, although it's
generally kind of completely
outside the topic of
this particular episode.
So we are allocating two
integers back to back
and putting two integers
on the stack back to back.
And we'll see what we see.
So let's see here.
We have the second
integer allocated--
or the first integer that's
allocated on the heap is here.
And then 10 hex, 10 bytes away.
So 16 bytes away, we've
got the second object
that was created on the heap.
This is not guaranteed at all,
but it worked out this way.
And then we can see
here, the first object
that we created on
the stack is at D4.
And then the second object we
created on the stack is at D8.
So we can observe
how these things
grow up and down in the code.
But beside the point.
So this is the point
of to_address though.
We can pass it any kind
of pointer-like thing,
and it can give us the address
of where that was created.
So we're going to try
one more thing here,
and we're going to
create an iterator
and see how that works.
So I'm creating a
vector of integers.
And I'm going to need
to include my header.
Now, let's see if
this can tell us
the address of the
underlying object when
it was passed an iterator
to the first object.
And I expect this to work,
because iterators are
kind of pointer-like things.
They have this arrow
operator overloaded.
And that's one of the
things that the to_address
can work with.
And yes, it does work here.
And we can see that
it is continuing
to be allocated 16 bytes
away down on the heap here.
And that's because vector
is doing heap allocation,
because that's what vector does.
And I guess we should expect,
if I do a plus 1 here--
because a vector is a
contiguous allocator-- then
we should be able to see
that this address is going
to be approximately four
bytes after the first one,
since we're on a platform
where int is 32 bits.
Yes, so 7, 8.
And then 7C.
B, C-- that is four bytes away.
So it's a pretty,
potentially, useful utility.
If you're working
in highly generic
code, something to be aware of.
And just one other
quick comment.
If we go back to
the definition here,
we can see that they made
two interesting design
decisions in the
implementation of this
for the standard library.
The first is that
they're using auto
right here, assuming this is
how the spec is actually done.
Now, when I've discussed
this kind of decision--
why don't more standard
library utilities
use auto return
types with people
who are involved in the
standard-- they say,
well, it's not as good
for SFINAE-friendliness.
And if you don't know what that
means, don't worry about it.
But they did choose auto here.
The other thing is
that only the version
that takes a raw pointer
and returns a raw pointer
is constexpr.
But as we just saw, this
does work with iterators.
And we can get constexpr
iterators to standard array
and C-style array objects.
So I don't know what
the decision was
to not make this
version constexpr,
because it definitely could
be and actually should be
for many possible real cases.
Although, if you need
the actual underlying
address of an object
in any constexpr code,
you're probably doing
something weird.
But anyhow, thanks for
watching this episode.
I hope you liked it and
learned something new.
