Date: Mon, 2 Sep 2024 15:50:07 +0100
To summarise your points, while its possible to create a syntax that allows
you to define a type-punning mechanism for individual PoD wrappers, that
cannot be used to reliably deduce if aggregate types would also be type
punnable.
I'm not sure I like the approach of stricter requirements on the storage
container - for example, I can't think of a reason why something like
`std::deque<my_distance_type>` couldn't be type-punned to
`std::deque<double>`.
However, dealing with specialisations based on PoD types would certainly
cause problems, and for dynamic data types, deducing if memory footprint is
equivalent is not feasible.
Instead, what if type-punning is disabled when the container specialisation
chosen for T and the PoD U are different?
Perhaps some facility taking a std::span or a ContiguousContainer or a
> contiguous_iterator and converting it en bloc?
On Mon, 2 Sept 2024 at 14:08, Sebastian Wittmeier via Std-Proposals <
std-proposals_at_[hidden]> wrote:
> Perhaps some facility taking a std::span or a ContiguousContainer or a
> contiguous_iterator and converting it en bloc?
>
>
>
> vector<bool> does not fulfill ContiguousContainer, so at least that
> example would be caught.
>
>
>
> -----Ursprüngliche Nachricht-----
> *Von:* Jonathan Wakely via Std-Proposals <std-proposals_at_[hidden]>
> *Gesendet:* Mo 02.09.2024 15:00
> *Betreff:* Re: [std-proposals] Fwd: Standardised Type Punning API for
> Wrapper Types
> *An:* std-proposals_at_[hidden];
> *CC:* Jonathan Wakely <cxx_at_[hidden]>;
>
>
> On Mon, 2 Sept 2024 at 13:40, Josh Warren via Std-Proposals <
> std-proposals_at_[hidden]> wrote:
>
> Hi std proposals,
>
> The Problem:
>
> It is common to "strongly type" PoD in C++ by wrapping a single value in a
> class/struct, therefore providing the compiler with a richer context for
> identifying misuse of the data stored therein. A good example of this is
> `std::chrono` which strongly types time related quantities to prevent
> logical errors which would otherwise not be caught at compile-time (e.g.
> conversions between seconds and minutes). However, this strong typing can
> become cumbersome when interfacing with code that doesn't use the same
> "strong type" for logically equivalent data. Mechanisms/backdoors exist to
> help circumvent this, such as `.count()` on `std::chrono::duration<>`
> types, but this a specialised solution, since there is no standard for how
> all such wrapper types should handle this issue. Also, that approach fails
> in the case of aggregate types, and we are forced to invoke UB. e.g.:
>
> #include <chrono>
>
> void old_c_api(long* times, unsigned long count)
> {
> for (int i = 0; i < count; ++i)
> {
> printf("%ld", times[i]);
> }
> }
> int main()
> {
> {
> using namespace std::chrono_literals;
> std::vector times{1s, 2s, 3s};
> old_c_api(reinterpret_cast<long*>(times.data()), times.size()); // UB, but do we have enough information for this to not need to be?
> }
>
> {
> std::vector times{4L, 5L, 6L};
> old_c_api(times.data(), times.size());
> }
> }
>
> Which prints the expected: 123456
>
> Proposed Solution:
>
> Introduce a new way of defining an explicit conversion operator which
> signals to the compiler that we would like the wrapper type to support type
> punning to the type of the stored data.
>
> #include <utility>
>
> struct my_distance_type
> {
> double value;
> explicit operator double() = default; // if the compiler can generate this, then type punning is allowed
> };
> void old_c_api(double* distance)
> {
> // ...
> }
> int main()
> {
> my_distance_type d{.value = 1.};
> double* d_ptr = &d; // check if type punning to the requested type is supported, if it is, treat d as a double
> old_c_api(d); // still not allowed since d cannot be implicitly converted to double, so we don't give up the strong typing as the default
> old_c_api(std::to_underlying(&d)); // add template specialisation for non-enum types that provide a compiler-generated explicit conversion operator
> }
>
>
>
> Then it would be possible to introduce type punning for aggregate types:
>
> #include <utility>
> #include <vector>
> #include <iostream>
>
> struct my_distance_type
> {
> double value;
> explicit operator double() = default; // if the compiler can generate this, then type punning is allowed
> };
>
> void cpp_api(std::vector<double>& distances)
> {
> for (auto element : distances)
> {
> std::cout << element << std::endl;
> }
> }
> int main()
> {
> std::vector<my_distance_type> d1;
> d1.emplace_back(1);
> d1.emplace_back(2);
> d1.emplace_back(3);
> cpp_api(reinterpret_cast<std::vector<double>&>(d1)); // UB again
> cpp_api(std::to_underlying(d1)); // if the value_types can be type punned, then this is equivalent to the line above without UB
> }
>
> The predicates for the explicit defaulted conversion operator would
> include:
>
> - std::is_trivially_destructible_v<wrapper<T>>
> - wrapper<T> must define a single data member of type T (don't think
> this is possible with <type_traits>)
>
> Any feedback is welcome, and thank you for reading this
>
>
>
> Being able to type-pun my_distance_type as double doesn't imply that you
> can type-pun vector<my_distance_type> to vector<double>.
>
> Consider a type which can type-pun as bool:
>
> struct bull {
> bool value;
> explicit operator bool() const = default;
> };
>
> vector<bull> and vector<bool> are not compatible.
>
>
>
> --
> Std-Proposals mailing list
> Std-Proposals_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>
>
> --
> Std-Proposals mailing list
> Std-Proposals_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>
you to define a type-punning mechanism for individual PoD wrappers, that
cannot be used to reliably deduce if aggregate types would also be type
punnable.
I'm not sure I like the approach of stricter requirements on the storage
container - for example, I can't think of a reason why something like
`std::deque<my_distance_type>` couldn't be type-punned to
`std::deque<double>`.
However, dealing with specialisations based on PoD types would certainly
cause problems, and for dynamic data types, deducing if memory footprint is
equivalent is not feasible.
Instead, what if type-punning is disabled when the container specialisation
chosen for T and the PoD U are different?
Perhaps some facility taking a std::span or a ContiguousContainer or a
> contiguous_iterator and converting it en bloc?
On Mon, 2 Sept 2024 at 14:08, Sebastian Wittmeier via Std-Proposals <
std-proposals_at_[hidden]> wrote:
> Perhaps some facility taking a std::span or a ContiguousContainer or a
> contiguous_iterator and converting it en bloc?
>
>
>
> vector<bool> does not fulfill ContiguousContainer, so at least that
> example would be caught.
>
>
>
> -----Ursprüngliche Nachricht-----
> *Von:* Jonathan Wakely via Std-Proposals <std-proposals_at_[hidden]>
> *Gesendet:* Mo 02.09.2024 15:00
> *Betreff:* Re: [std-proposals] Fwd: Standardised Type Punning API for
> Wrapper Types
> *An:* std-proposals_at_[hidden];
> *CC:* Jonathan Wakely <cxx_at_[hidden]>;
>
>
> On Mon, 2 Sept 2024 at 13:40, Josh Warren via Std-Proposals <
> std-proposals_at_[hidden]> wrote:
>
> Hi std proposals,
>
> The Problem:
>
> It is common to "strongly type" PoD in C++ by wrapping a single value in a
> class/struct, therefore providing the compiler with a richer context for
> identifying misuse of the data stored therein. A good example of this is
> `std::chrono` which strongly types time related quantities to prevent
> logical errors which would otherwise not be caught at compile-time (e.g.
> conversions between seconds and minutes). However, this strong typing can
> become cumbersome when interfacing with code that doesn't use the same
> "strong type" for logically equivalent data. Mechanisms/backdoors exist to
> help circumvent this, such as `.count()` on `std::chrono::duration<>`
> types, but this a specialised solution, since there is no standard for how
> all such wrapper types should handle this issue. Also, that approach fails
> in the case of aggregate types, and we are forced to invoke UB. e.g.:
>
> #include <chrono>
>
> void old_c_api(long* times, unsigned long count)
> {
> for (int i = 0; i < count; ++i)
> {
> printf("%ld", times[i]);
> }
> }
> int main()
> {
> {
> using namespace std::chrono_literals;
> std::vector times{1s, 2s, 3s};
> old_c_api(reinterpret_cast<long*>(times.data()), times.size()); // UB, but do we have enough information for this to not need to be?
> }
>
> {
> std::vector times{4L, 5L, 6L};
> old_c_api(times.data(), times.size());
> }
> }
>
> Which prints the expected: 123456
>
> Proposed Solution:
>
> Introduce a new way of defining an explicit conversion operator which
> signals to the compiler that we would like the wrapper type to support type
> punning to the type of the stored data.
>
> #include <utility>
>
> struct my_distance_type
> {
> double value;
> explicit operator double() = default; // if the compiler can generate this, then type punning is allowed
> };
> void old_c_api(double* distance)
> {
> // ...
> }
> int main()
> {
> my_distance_type d{.value = 1.};
> double* d_ptr = &d; // check if type punning to the requested type is supported, if it is, treat d as a double
> old_c_api(d); // still not allowed since d cannot be implicitly converted to double, so we don't give up the strong typing as the default
> old_c_api(std::to_underlying(&d)); // add template specialisation for non-enum types that provide a compiler-generated explicit conversion operator
> }
>
>
>
> Then it would be possible to introduce type punning for aggregate types:
>
> #include <utility>
> #include <vector>
> #include <iostream>
>
> struct my_distance_type
> {
> double value;
> explicit operator double() = default; // if the compiler can generate this, then type punning is allowed
> };
>
> void cpp_api(std::vector<double>& distances)
> {
> for (auto element : distances)
> {
> std::cout << element << std::endl;
> }
> }
> int main()
> {
> std::vector<my_distance_type> d1;
> d1.emplace_back(1);
> d1.emplace_back(2);
> d1.emplace_back(3);
> cpp_api(reinterpret_cast<std::vector<double>&>(d1)); // UB again
> cpp_api(std::to_underlying(d1)); // if the value_types can be type punned, then this is equivalent to the line above without UB
> }
>
> The predicates for the explicit defaulted conversion operator would
> include:
>
> - std::is_trivially_destructible_v<wrapper<T>>
> - wrapper<T> must define a single data member of type T (don't think
> this is possible with <type_traits>)
>
> Any feedback is welcome, and thank you for reading this
>
>
>
> Being able to type-pun my_distance_type as double doesn't imply that you
> can type-pun vector<my_distance_type> to vector<double>.
>
> Consider a type which can type-pun as bool:
>
> struct bull {
> bool value;
> explicit operator bool() const = default;
> };
>
> vector<bull> and vector<bool> are not compatible.
>
>
>
> --
> Std-Proposals mailing list
> Std-Proposals_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>
>
> --
> Std-Proposals mailing list
> Std-Proposals_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>
Received on 2024-09-02 14:50:22