Skip to content

Commit f2764ff

Browse files
committed
Rename ToBits to Transmogrify
In lack of a better name, we defer to Calvin and Hobbes to show us how to convert things to other types, through the Transmogrifier. The sus::mog<T>(f) operation will convert an object `f` to an object of type T, performing a copy as needed. The mog operation is supported for subspace numeric types both integers and floats, primitive integer and float types, and enums and enum classes. The operation can be applied to other types by providing a specialization of the TransmogrifyImpl<To, From> struct with a To mog_from(const From&) static method.
1 parent ade3414 commit f2764ff

20 files changed

+982
-911
lines changed

subspace/CMakeLists.txt

+2-1
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ target_sources(subspace PUBLIC
156156
"num/__private/unsigned_integer_consts.inc"
157157
"num/__private/unsigned_integer_methods.inc"
158158
"num/__private/unsigned_integer_methods_impl.inc"
159+
"num/transmogrify.h"
159160
"num/float.h"
160161
"num/float_concepts.h"
161162
"num/float_impl.h"
@@ -259,7 +260,7 @@ if(${SUBSPACE_BUILD_TESTS})
259260
"mem/take_unittest.cc"
260261
"num/__private/literals_unittest.cc"
261262
"num/cmath_macros_unittest.cc"
262-
"num/convert_unittest.cc"
263+
"num/transmogrify_unittest.cc"
263264
"num/f32_unittest.cc"
264265
"num/f64_unittest.cc"
265266
"num/i8_unittest.cc"

subspace/construct/to_bits.h

+67-32
Original file line numberDiff line numberDiff line change
@@ -16,47 +16,62 @@
1616

1717
#include <concepts>
1818

19+
#include "subspace/mem/copy.h"
20+
1921
namespace sus::construct {
2022

2123
/// Specializing this class for `To` and `From` allows `To` to satisfy
22-
/// `ToBits<To, From>`.
24+
/// `Transmogrify<To, From>`.
2325
///
2426
/// # Examples
2527
///
2628
/// To allow bitwise conversion to `Goat` from any type satisying a
2729
/// concept `GoatLike`:
2830
/// ```cpp
29-
/// // Satisfies ToBits<Goat, GoatLike>.
31+
/// // Satisfies Transmogrify<Goat, GoatLike>.
3032
/// template <class Goat, GoatLike G>
31-
/// struct ToBitsImpl<Goat, G> {
32-
/// constexpr static Goat from_bits(const G& g) noexcept { return ...; }
33+
/// struct TransmogrifyImpl<Goat, G> {
34+
/// constexpr static Goat mog_from(const G& g) noexcept { return ...; }
3335
/// };
3436
/// ```
3537
///
3638
/// To receive something that can be bitwise converted to an `u32`.
3739
/// ```cpp
38-
/// auto add = [](u32 a, const sus::construct::ToBits<u32> auto& b) -> u32 {
39-
/// return a.wrapping_add(sus::to_bits<u32>(b));
40+
/// auto add = [](u32 a, const sus::construct::Transmogrify<u32> auto& b) -> u32
41+
/// {
42+
/// return a.wrapping_add(sus::mog<u32>(b));
4043
/// };
4144
/// sus::check(add(3_u32, -1_i32) == u32::MIN + 2);
4245
/// ```
4346
template <class To, class From>
44-
struct ToBitsImpl;
47+
struct TransmogrifyImpl;
4548

46-
// sus::construct::ToBits<T, T> trait for identity conversion.
47-
template <class T>
48-
struct ToBitsImpl<T, T> {
49-
constexpr static T from_bits(const T& from) noexcept { return from; }
49+
// sus::construct::Transmogrify<T, T> trait for identity conversion when `T` is
50+
// `Copy`.
51+
template <::sus::mem::Copy T>
52+
struct TransmogrifyImpl<T, T> {
53+
constexpr static T mog_from(const T& from) noexcept { return from; }
5054
};
5155

52-
/// A type `T` that satisfies `ToBits<T, F>` can be constructed from `F` through
53-
/// a conversion that will always succeed in producing _some_ value, but may be
54-
/// lossy or produce a value with a different meaning. The conversion may
55-
/// truncate or extend `F` in order to do the conversion to `T`.
56+
/// When a pair of types `T` and `F` satisfy `Transmogrify<T, F>`, it means that
57+
/// `F` can be converted
58+
/// ([transmogrified](https://calvinandhobbes.fandom.com/wiki/Transmogrifier))
59+
/// to `T` through a conversion that will always succeed in producing _some_
60+
/// value, but may be lossy or produce a value with a different meaning. The
61+
/// conversion may truncate or extend `F` in order to do the conversion to `T`.
62+
///
63+
/// This operation is also commonly known as type casting, or type coercion. The
64+
/// conversion to `T` can be done by calling `sus::mog<T>(from)`.
5665
///
57-
/// For numeric and primitive types, this provides a mechanism like
58-
/// `static_cast<T>` but it is much safer than `static_cast<T>` as it has
59-
/// defined behaviour for all inputs:
66+
/// The conversion is defined for the identity conversion where both the input
67+
/// and output are the same type, if the type is `Copy`, in which case the input
68+
/// is copied and returned. As Transmogrification is meant to be a cheap
69+
/// conversion, primarily for primitive types, it does not support `Clone`
70+
/// types, and `sus::construct::Into` should be used in more complex cases.
71+
///
72+
/// For numeric and primitive types, `Transmogrify` is defined to provide a
73+
/// mechanism like `static_cast<T>` but it is much safer than `static_cast<T>`
74+
/// as it has defined behaviour for all inputs:
6075
///
6176
/// * Casting from a float to an integer will perform a static_cast, which
6277
/// rounds the float towards zero, except:
@@ -81,42 +96,62 @@ struct ToBitsImpl<T, T> {
8196
/// from `u8`.
8297
///
8398
/// These conversions are all defined in `subspace/num/types.h`.
99+
///
100+
/// The transmogrifier is one of three of the most complicated inventions. The
101+
/// other two are the [Cerebral
102+
/// Enhance-O-Tron](https://calvinandhobbes.fandom.com/wiki/Cerebral_Enhance-O-Tron),
103+
/// and the [Transmogrifier
104+
/// Gun](https://calvinandhobbes.fandom.com/wiki/Transmogrifier_Gun).
105+
///
106+
/// # Extending to other types
107+
///
108+
/// Types can participate in defining their transmogrification strategy by
109+
/// providing a specialization of `sus::convert::TransmogrifyImpl<To, From>`.
110+
/// The conversions should always produce a value of type `T`, should not panic,
111+
/// and should not cause Undefined Behaviour.
112+
///
113+
/// The `Transmogrify` specialization needs a static method `mog_from()` that
114+
/// receives `const From&` and returns `To`.
84115
template <class To, class From>
85-
concept ToBits = requires(const From& from) {
116+
concept Transmogrify = requires(const From& from) {
86117
{
87-
::sus::construct::ToBitsImpl<To, From>::from_bits(from)
118+
::sus::construct::TransmogrifyImpl<To, From>::mog_from(from)
88119
} noexcept -> std::same_as<To>;
89120
};
90121

91-
/// An infallible conversion that may lose the original value in the process. If
92-
/// the input can not be represented in the output, some other value will be
93-
/// produced, which may lead to application bugs and memory unsafety if used
94-
/// incorrectly.
122+
/// An infallible conversion (transmogrification) that may lose the original
123+
/// value in the process. If the input can not be represented in the output,
124+
/// some other value will be produced, which may lead to application bugs and
125+
/// memory unsafety if used incorrectly. This behaves like `static_cast<To>()`
126+
/// but without Undefined Behaviour.
127+
///
128+
/// The `mog` operation is supported for types `To` and `From` that satisfy
129+
/// `Transmogrify<To, From>`.
95130
///
96131
/// To convert between types while ensuring the values are preserved, use
97132
/// `sus::construct::Into` or `sus::construct::TryInto`. Usually prefer using
98-
/// `sus::into(x)` or `sus::try_into(x)` over `sus::to_bits<Y>(x)` as most code
133+
/// `sus::into(x)` or `sus::try_into(x)` over `sus::mog<Y>(x)` as most code
99134
/// should preserve values across type transitions.
100135
///
101-
/// See `AsBits` for how numeric and primitive values are converted.
136+
/// See `Transmogrify` for how numeric and primitive values are converted.
102137
///
103138
/// # Examples
104139
///
105140
/// This converts `-1_i64` into a `u32`, which both changes its meaning,
106141
/// becoming a large positive number, and truncates the high 32 bits, losing the
107142
/// original.
108143
/// ```cpp
109-
/// sus::check(u32::MAX == sus::to_bits<u32>(-1_i64));
144+
/// sus::check(u32::MAX == sus::mog<u32>(-1_i64));
110145
/// ```
111146
template <class To, class From>
112-
requires(ToBits<To, From>)
113-
constexpr inline To to_bits(const From& from) {
114-
return ToBitsImpl<To, From>::from_bits(from);
147+
requires(Transmogrify<To, From>)
148+
constexpr inline To mog(const From& from) {
149+
return TransmogrifyImpl<To, From>::mog_from(from);
115150
}
116151

117152
} // namespace sus::construct
118153

119-
// Bring the to_bits() function into the `sus` namespace.
154+
// Bring the mog() function into the `sus` namespace.
120155
namespace sus {
121-
using ::sus::construct::to_bits;
156+
using ::sus::construct::mog;
122157
}

subspace/construct/to_bits_unittest.cc

+5-5
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,15 @@
1919

2020
namespace {
2121

22-
TEST(ToBits, Example_Concept) {
23-
auto add = [](u32 a, const sus::construct::ToBits<u32> auto& b) -> u32 {
24-
return a.wrapping_add(sus::to_bits<u32>(b));
22+
TEST(Transmogrify, Example_Concept) {
23+
auto add = [](u32 a, const sus::construct::Transmogrify<u32> auto& b) -> u32 {
24+
return a.wrapping_add(sus::mog<u32>(b));
2525
};
2626
sus::check(add(3_u32, -1_i32) == u32::MIN + 2u);
2727
}
2828

29-
TEST(ToBits, Example_Function) {
30-
sus::check(u32::MAX == sus::to_bits<u32>(-1_i64));
29+
TEST(Transmogrify, Example_Function) {
30+
sus::check(u32::MAX == sus::mog<u32>(-1_i64));
3131
}
3232

3333
} // namespace

subspace/containers/array.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ struct Storage<T, 0> final {};
6969
/// greater distance results in Undefined Behaviour.
7070
template <class T, size_t N>
7171
class Array final {
72-
static_assert(N <= ::sus::to_bits<usize>(isize::MAX));
72+
static_assert(N <= ::sus::mog<usize>(isize::MAX));
7373
static_assert(!std::is_reference_v<T>,
7474
"Array<T&, N> is invalid as Array must hold value types. Use "
7575
"Array<T*, N> instead.");

subspace/containers/slice.h

+5-5
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
#include "subspace/containers/iterators/split.h"
2828
#include "subspace/containers/iterators/windows.h"
2929
#include "subspace/containers/join.h"
30-
#include "subspace/num/convert.h"
30+
#include "subspace/num/transmogrify.h"
3131
#include "subspace/fn/fn_concepts.h"
3232
#include "subspace/fn/fn_ref.h"
3333
#include "subspace/iter/iterator_defn.h"
@@ -110,7 +110,7 @@ class [[sus_trivial_abi]] Slice final {
110110
sus_pure static constexpr inline Slice from_raw_parts(
111111
::sus::marker::UnsafeFnMarker, ::sus::iter::IterRefCounter refs,
112112
const T* data sus_lifetimebound, usize len) noexcept {
113-
::sus::check(len <= ::sus::to_bits<usize>(isize::MAX));
113+
::sus::check(len <= ::sus::mog<usize>(isize::MAX));
114114
// We strip the `const` off `data`, however only const access is provided
115115
// through this class. This is done so that mutable types can compose Slice
116116
// and store a mutable pointer.
@@ -123,7 +123,7 @@ class [[sus_trivial_abi]] Slice final {
123123
///
124124
/// #[doc.overloads=from.array]
125125
template <size_t N>
126-
requires(N <= ::sus::to_bits<usize>(isize::MAX))
126+
requires(N <= ::sus::mog<usize>(isize::MAX))
127127
sus_pure static constexpr inline Slice from(
128128
const T (&data)[N] sus_lifetimebound) {
129129
// We strip the `const` off `data`, however only const access is provided
@@ -319,7 +319,7 @@ class [[sus_trivial_abi]] SliceMut final {
319319
sus_pure static constexpr inline SliceMut from_raw_parts_mut(
320320
::sus::marker::UnsafeFnMarker, ::sus::iter::IterRefCounter refs,
321321
T* data sus_lifetimebound, usize len) noexcept {
322-
::sus::check(len <= ::sus::to_bits<usize>(isize::MAX));
322+
::sus::check(len <= ::sus::mog<usize>(isize::MAX));
323323
return SliceMut(::sus::move(refs), data, len);
324324
}
325325

@@ -329,7 +329,7 @@ class [[sus_trivial_abi]] SliceMut final {
329329
///
330330
/// #[doc.overloads=from.array]
331331
template <size_t N>
332-
requires(N <= ::sus::to_bits<usize>(isize::MAX_PRIMITIVE))
332+
requires(N <= ::sus::mog<usize>(isize::MAX_PRIMITIVE))
333333
sus_pure static constexpr inline SliceMut from(
334334
T (&data)[N] sus_lifetimebound) {
335335
return SliceMut(::sus::iter::IterRefCounter::empty_for_view(), data, N);

subspace/containers/vec.h

+3-3
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
#include "subspace/mem/relocate.h"
4545
#include "subspace/mem/replace.h"
4646
#include "subspace/mem/size_of.h"
47-
#include "subspace/num/convert.h"
47+
#include "subspace/num/transmogrify.h"
4848
#include "subspace/num/integer_concepts.h"
4949
#include "subspace/num/signed_integer.h"
5050
#include "subspace/num/unsigned_integer.h"
@@ -116,7 +116,7 @@ class Vec final {
116116
/// Panics if the capacity exceeds `isize::MAX` bytes.
117117
sus_pure static inline constexpr Vec with_capacity(usize capacity) noexcept {
118118
check(::sus::mem::size_of<T>() * capacity <=
119-
::sus::to_bits<usize>(isize::MAX));
119+
::sus::mog<usize>(isize::MAX));
120120
auto v = Vec(nullptr, 0_usize, 0_usize);
121121
// TODO: Consider rounding up to nearest 2^N for some N? A min capacity?
122122
v.grow_to_exact(capacity);
@@ -427,7 +427,7 @@ class Vec final {
427427
check(!has_iterators());
428428
if (cap <= capacity_) return; // Nothing to do.
429429
const auto bytes = ::sus::mem::size_of<T>() * cap;
430-
check(bytes <= ::sus::to_bits<usize>(isize::MAX));
430+
check(bytes <= ::sus::mog<usize>(isize::MAX));
431431
if (!is_alloced()) {
432432
raw_data() = static_cast<T*>(malloc(bytes.primitive_value));
433433
} else {

subspace/iter/compat_ranges.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
#include "subspace/macros/__private/compiler_bugs.h"
2525
#include "subspace/macros/lifetimebound.h"
2626
#include "subspace/mem/move.h"
27-
#include "subspace/num/convert.h"
27+
#include "subspace/num/transmogrify.h"
2828
#include "subspace/num/unsigned_integer.h"
2929
#include "subspace/option/option.h"
3030

subspace/num/__private/float_methods.inc

+42-7
Original file line numberDiff line numberDiff line change
@@ -208,8 +208,8 @@ static constexpr _self from_product(
208208
///
209209
/// Typically this should be used in a curly-brace conversion statement such as
210210
/// `float{3_f32}` which will ensure a compiler error if the conversion can lose
211-
/// data. Lossy conversions can be done through the `sus::convert::ToBits`
212-
/// concept, such as `sus::to_bits<float>(3_f32)`.
211+
/// data. Lossy conversions can be done through the `sus::convert::Transmogrify`
212+
/// concept, such as `sus::mog<float>(3_f32)`.
213213
template <PrimitiveFloat U>
214214
requires(::sus::mem::size_of<U>() >= ::sus::mem::size_of<_primitive>())
215215
sus_pure constexpr inline explicit operator U() const {
@@ -288,7 +288,8 @@ template <Float F>
288288
/// IEEE 754 standard, which may not match the interpretation by some of the
289289
/// older, non-conformant (e.g. MIPS) hardware implementations.
290290
sus_pure constexpr std::strong_ordering total_cmp(_self other) const& noexcept {
291-
return __private::float_strong_ordering(primitive_value, other.primitive_value);
291+
return __private::float_strong_ordering(primitive_value,
292+
other.primitive_value);
292293
}
293294

294295
/// sus::num::Neg<##_self##> trait.
@@ -650,8 +651,35 @@ sus_pure constexpr inline I to_int_unchecked(
650651

651652
/// Raw transmutation from `##_unsigned##`.
652653
///
653-
/// Note that this function is distinct from Into<##_self##>, which attempts to
654-
/// preserve the numeric value, and not the bitwise value.
654+
/// This is identical to [`std::bit_cast<f32,
655+
/// u32>`](https://en.cppreference.com/w/cpp/numeric/bit_cast), or
656+
/// `std::bit_cast<f64, u64>`. It turns out this is incredibly portable, for two
657+
/// reasons:
658+
///
659+
/// * Floats and Ints have the same endianness on all modern platforms.
660+
/// * IEEE 754 very precisely specifies the bit layout of floats.
661+
///
662+
/// However there is one caveat: prior to the 2008 version of IEEE 754, how to
663+
/// interpret the NaN signaling bit wasn’t actually specified. Most platforms
664+
/// (notably x86 and ARM) picked the interpretation that was ultimately
665+
/// standardized in 2008, but some didn’t (notably MIPS). As a result, all
666+
/// signaling NaNs on MIPS are quiet NaNs on x86, and vice-versa.
667+
///
668+
/// Rather than trying to preserve signaling-ness cross-platform, this
669+
/// implementation favors preserving the exact bits. This means that any
670+
/// payloads encoded in NaNs will be preserved even if the result of this method
671+
/// is sent over the network from an x86 machine to a MIPS one.
672+
///
673+
/// If the results of this method are only manipulated by the same architecture
674+
/// that produced them, then there is no portability concern.
675+
///
676+
/// If the input isn’t NaN, then there is no portability concern.
677+
///
678+
/// If you don’t care about signalingness (very likely), then there is no
679+
/// portability concern.
680+
///
681+
/// Note that this function is distinct from `Transmogrify` casting, which
682+
/// attempts to preserve the *numeric* value, and not the bitwise value.
655683
///
656684
/// # Examples
657685
/// ```
@@ -666,8 +694,15 @@ sus_pure static _self from_bits(_unsigned v) noexcept {
666694
}
667695
/// Raw transmutation to ##UnsignedT##.
668696
///
669-
/// Note that this function is distinct from Into<##_unsigned##>, which
670-
/// attempts to preserve the numeric value, and not the bitwise value.
697+
/// This is identical to [`std::bit_cast<u32,
698+
/// f32>`](https://en.cppreference.com/w/cpp/numeric/bit_cast), or
699+
/// `std::bit_cast<u64, f64>`.
700+
///
701+
/// See `from_bits()` for some discussion of the portability of this operation
702+
/// (there are almost no issues).
703+
///
704+
/// Note that this function is distinct from `Transmogrify` casting, which
705+
/// attempts to preserve the *numeric* value, and not the bitwise value.
671706
sus_pure constexpr inline _unsigned to_bits() const& noexcept {
672707
return std::bit_cast<decltype(_unsigned::primitive_value)>(primitive_value);
673708
}

0 commit comments

Comments
 (0)