C++ Logo

std-proposals

Advanced search

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

From: Sebastian Wittmeier <wittmeier_at_[hidden]>
Date: Mon, 2 Sep 2024 15:08:47 +0200
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] <mailto: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  

Received on 2024-09-02 13:08:49