C++ Logo

std-discussion

Advanced search

Re: Casting pointers in constant evaluation

From: Keenan Horrigan <friedkeenan_at_[hidden]>
Date: Wed, 06 Oct 2021 19:51:44 +0000
> `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++.

First of all, not all my use cases for reinterpret_cast in constexpr are for implementing the standard library. I would probably doubt that the committee would find those use cases compelling, but they exist. Second, I'd agree that yes reinterpret_cast should in most cases not be used, however, as you said, it is something we sometimes need. Furthermore, the compiler would be required to detect the uses of reinterpret_cast that eventually lead to UB, so in constexpr code it would be necessarily safe on that front.

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

Interesting, I did not not know that. I checked and it's actually only defined to have the same *result* as static_casting to and from a void pointer when converting between object pointers, but that's the case we're talking about here.

> 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.

I tried this, but it turns out it doesn't work for what I need. I need an array of uninitialized objects, and wrapping an object with a union is the only way to ensure an uninitialized object in the language as far as I know. When allocating an array of unions and then getting a pointer to the member, the constexpr interpreter treats it as a pointer to a single object when I need it as a pointer to contiguous objects. And again, as far as I can tell, this behavior is well-defined in the standard at runtime. The pointer of a union is pointer-interconvertible to a pointer to one of its non-static data members. I would try to put the array inside the union, but I need a runtime length for the array, so that's not gonna work either.

While I would certainly enjoy more STL "wrappers" for reinterpret_cast's legitimate uses, the standardization process is slow, imperfect, and doesn't cater to every individual's needs. So allowing users to reinterpret_cast on their own in constexpr and the compiler ensuring that their code is well-defined would allow developers to take advantage of those well-defined use cases without the need to wait for the committee to standardize some magic wrapper or to just eventually reject the idea.

‐‐‐‐‐‐‐ Original Message ‐‐‐‐‐‐‐

On Wednesday, October 6th, 2021 at 10:07 AM, Jason McKesson via Std-Discussion <std-discussion_at_[hidden]> wrote:

> 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.
> ------------------------------
>
> Std-Discussion mailing list
>
> Std-Discussion_at_[hidden]
>
> https://lists.isocpp.org/mailman/listinfo.cgi/std-discussion

Received on 2021-10-06 14:51:51