Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

reinterpret_cast to span of wrappers

Given the following wrapper (simplified):

template <std::integral T> class Wrapper {
    public:  auto val() { return t; }
    private: T t;
};

I am trying to obtain a span of wrappers to a container of the wrapped types, like:

template <std::integral T>
auto suspect(auto& ar) {
    return std::span(reinterpret_cast<Wrapper<T>*>(ar.data()), ar.size());
}

to be used like this:

auto ar = external_func_returning_container();
auto sp = suspect<int>(ar);
external_func_accepting_span_of_wrappers(sp);

(Note: My current use-case is a protobuf repeated field, but it can be any container that I have no control over).

My understanding is that since Wrapper standard layout it is possible to cast a pointer to it to a pointer to it's wrapped member.

However, I strongly suspect that the suspect() template as written invokes undefined behaviour, mostly because there are no actual Wrapper objects.

My questions are:

  1. Am I right in believing that suspect() invokes UB?

  2. If so, is there a way to rewrite it without invoking UB or copying a potentially large container (using C++20 or C++23 features)?

  3. If not possible to do it in a portable way, are there any Clang-specific way to accomplish it?


Edit:

Will placement new work?

template <std::integral T>
auto suspect(auto& ar) {
    return std::span(new(ar.data()) Wrapper<T>[ar.size()], ar.size());
}

Hmmm... it appears that, according to [expr.new] (21.4), array placement new can add an unspecified overhead, so it may not work for this purpose. Or maybe I misunderstand?


Edit 2: (in response to comments and answers)

I am currently using Clang17, which does not support std::start_lifetime_as.

Actually, according to cppreference, no compiler currently supports it.

However, a SO answer suggests that it can be implemented as follows:

template<class T>
requires (std::is_trivially_copyable_v<T> && std::is_implicit_lifetime_v<T>)
T* start_lifetime_as(void* p) noexcept
{
    return std::launder(static_cast<T*>(std::memmove(p, p, sizeof(T))));
}

But it seems that I need a start_lifetime_as_array instead, and I am not sure how to implement it.

like image 887
Alex O Avatar asked Oct 15 '25 19:10

Alex O


1 Answers

I'll assume that you mean that ar's value type is T.

My understanding is that since Wrapper standard layout it is possible to cast a pointer to it to a pointer to it's wrapped member.

That's correct (if T is itself standard-layout, but you are doing the other way around and if there is no Wrapped<T> object to begin with, then it doesn't matter that you could obtain a pointe to the wrapped object in that way. It simply doesn't exist.

Using the result of this cast is always UB.

Am I right in believing that suspect() invokes UB?

Yes.

If so, is there a way to rewrite it without invoking UB or copying a potentially large container (using C++20 or C++23 features)?

By applying std::start_lifetime_as to the storage of ar. However, in general, that must be implemented in the container ar or you need to apply std::start_lifetime_as again to revert back to T before the container will try to perform any non-trivial action on the memory again. After the call to std::start_lifetime_as, generally, only access through Wrapped<T> will be allowed.

If not possible to do it in a portable way, are there any Clang-specific way to accomplish it?

Not that I am aware of.


Note that compilers do definitively make use of this UB if T is not standard-layout and even if it is standard-layout I am not 100% sure that they don't. Being standard-layout the compiler can not make use of the UB when seeing a pointer to T and one to Wrapped<T> for pointer aliasing analysis. However, if it can see the origin of the pointer, it could in principle do that.


Sorry, I missed that you have a constraint of std::integral on T. Then T is of course standard-layout and the worst case scenario does not apply.

like image 161
user17732522 Avatar answered Oct 18 '25 09:10

user17732522



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!