C++ Logo

std-proposals

Advanced search

Re: [std-proposals] Fwd: Standardised Type Punning API for Wrapper Types

From: Jonathan Wakely <cxx_at_[hidden]>
Date: Mon, 2 Sep 2024 13:59:22 +0100
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.

Received on 2024-09-02 13:00:39