That is part of the reason I said an operation like std::start_lifetime_as(). The goal is to temporarily replace objects of type A with objects of type B while preserving the object representation, then accessing/modifying the replacement objects using only type B, then replace the objects of type B with objects of type A while again preserving the object representation in such a way that pointers/references to the original set of objects transparently refer to the new set of objects. Key to making this work is specifying that these operations do not end the lifetime of any enclosing objects.In my opinion, unless the non-aliasing type restrictions are removed,Yes, the "unless" idea was my original thought that I emailed to this list. The types double and std::float64_t are different; one is not a typedef of the other. But, we could say the two types may alias analogously to the way double and std::complex<double> may alias. Then, as Tiago F suggests, reinterpret_cast should work.the following is required: An operation very much like std::start_lifetime_as() is needed in place of reinterpret_cast. Another operation, perhaps the same operation with types reversed, must be written at the point where the programmer wants changes to be synchronized. In between those two operations, the memory must only be accessed via std::float64_t. Before and after both operations, the memory must only be accessed via double.A difficulty is that std::start_lifetime_as_array() returns a pointer. I don't think one may use the old pointer to refer to objects of the new lifetime type. Or, maybe you can if you launder it?
The member function std::vector::data() returns a pointer, not a reference to the pointer. So, how do we jam the result of a proposed std::cast_unchecked<double*>(std::float64_t*) into the vector? Or, do we propose std::vector::launder()?
No need to touch std::vector. Your daxpy() example would have to be written something like this.
void daxpy(std::size_t n, const std::float64_t alpha, const std::float64_t* x,
std::float64_t* y) {
for (std::size_t i = 0; i < n; ++i) y[i] += alpha * x[i];
}
int main() {
std::size_t n = 40;
double alpha = 2.0;
std::vector<double> x(n, 1.0);
std::vector<double> y(n, 3.0);
std::inplace_converting_array_cast<std::float64_t>(x.data(), x.size());
std::inplace_converting_array_cast<std::float64_t>(y.data(), y.size());
daxpy(n, std::bitcast<std::float64_t>(alpha), x.data(), y.data());
std::inplace_converting_array_cast<double>(y.data(), y.size());
std::inplace_converting_array_cast<double>(x.data(), x.size());
std::cout << y[17] << '\n'; // UB
}
This is becoming unteachable.
Definitely. Anything we do here will be an expert level feature
with high potential for misuse. The challenge then is to wrap it
in an interface that minimizes the foot gun potential. An RAII
interface with support for ranges would allow the example above to
be written more cleanly, but the RAII object would have to be
scoped correctly to ensure objects of the right type are in place
prior to evaluation of y[17]; it
would be easy to forget to add the needed compound statement.
Another option would be to use an RAII interface that requires an
explicit call to "undo" the initial conversion operation with
failure to do so resulting in a contract violation for the
destructor (such that the destructor throws an exception, aborts
the program, etc...). The best option would require some kind of
static analysis to ensure that an "undo" call is present on all
control paths; that would require a language-based interface
rather than a library one.
Tom.