C++ Logo

std-discussion

Advanced search

Re: Casting pointers in constant evaluation

From: Jason McKesson <jmckesson_at_[hidden]>
Date: Wed, 6 Oct 2021 10:07:41 -0400
On Wed, Oct 6, 2021 at 5:18 AM Keenan Horrigan via Std-Discussion
<std-discussion_at_[hidden]> wrote:
>
> In constant expressions, it's possible to static_cast between certain pointers, e.g. between a pointer to a derived object and a pointer to a base object.
>
> But, notably, you cannot reinterpret_cast in constant expressions. I've run into this before, but generally I've been able to get around this constraint by using std::bit_cast. However, if e.g. you want to make a constexpr (and therefore compliant) implementation of std::addressof, you need compiler magic, since a pure language implementation, as far as I can tell, requires reinterpret_cast. This is where I start to not like this constraint: reinterpret_cast is disallowed in constant expressions even when it's not undefined behavior, i.e. when you cast to a pointer to an aliasing type. Why is this?

There are two reasons:

1) The reinterpret_cast *itself* isn't UB. If the cast itself were
100% known to be wrong, it would just be il-formed. It's the *use* of
the result of the cast that is UB. If you cast from an `A*` to an
unrelated type `B*`, that's only bad if you try to access a `B`
through that pointer. If you cast it back to an `A*` before using it,
that's well-defined.

2) reinterpret_cast almost always means you are doing something dodgy.

You can't implement constexpr `bit_cast` without compiler magic
either. There's no expectation that every part of the standard library
will be implementable in the language, *especially* the parts required
for a freestanding implementation. And while in many cases it would be
good if we could (ie: most type_traits could be implemented if we had
reflection), that's mainly good because *reflection* is good in and of
itself.

`reinterpret_cast` isn't good. It's a dangerous tool that we sometimes
need, but its use should be contained. We shouldn't shove it into
constexpr code just so that we can implement parts of the standard
library in pure C++.

> Did the committee not want to put too much burden on compilers to keep track of when the use of reinterpret_cast would be UB? That's all I can think of, as surely in constant evaluation objects still have their underlying data, as evidenced by std::bit_cast? Or is it because in constant evaluation pointers act more like rebindable references rather than addresses to memory? I could certainly understand disallowing casting to e.g. std::uintptr_t for that reason, but not so much pointers to aliasing types.
>
> Another fun fact about casting pointers in constant expressions: static_casting from cv-qualified void pointer is disallowed.

That's how `reinterpret_cast` on pointers is *defined*. If you allowed
this, then you would be allowing `reinterpret_cast`.

> For this I think I can see why it might be desirable to do this, as a void pointer erases the type of the pointed-to object, but I disagree that this should be disallowed. There are in fact non-UB uses of casting to and from void pointers. For instance, as far as I can tell, casting a pointer to a union to a void pointer and then casting to a pointer to one of the union's elements is not UB.

But why would you need to do that? Why can't you just get the address
of the active member? The code would then actually make sense.

> This is useful for a constexpr implementation of std::allocator in pure C++ as you can use a union to have an uninitialized object.

I'm not sure how that works.

Received on 2021-10-06 09:08:19