Date: Tue, 26 Aug 2025 23:50:39 -0400
On 8/26/25 4:43 PM, Paul Caprioli via Std-Proposals wrote:
>> 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 andstd::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 andstd::complex<double> may alias.
> Then, as Tiago F suggests, reinterpret_cast should work.
>
>> the following is required:
>>
>> An operation very much likestd::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 viastd::float64_t.
>> Before and after both operations, the memory must only be accessed via double.
> A difficulty is thatstd::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?
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.
> The member functionstd::vector::data() returns a pointer, not a reference to the pointer.
> So, how do we jam the result of a proposedstd::cast_unchecked<double*>(std::float64_t*) into the vector?
> Or, do we proposestd::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.
>> 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 andstd::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 andstd::complex<double> may alias.
> Then, as Tiago F suggests, reinterpret_cast should work.
>
>> the following is required:
>>
>> An operation very much likestd::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 viastd::float64_t.
>> Before and after both operations, the memory must only be accessed via double.
> A difficulty is thatstd::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?
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.
> The member functionstd::vector::data() returns a pointer, not a reference to the pointer.
> So, how do we jam the result of a proposedstd::cast_unchecked<double*>(std::float64_t*) into the vector?
> Or, do we proposestd::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.
Received on 2025-08-27 03:50:42