1 // Copyright 2024 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14 #pragma once
15
16 #include <memory>
17 #include <new>
18 #include <type_traits>
19 #include <utility>
20
21 namespace pw::internal {
22
23 // Equivalent to C++20's std::derived_from concept.
24 template <typename Derived, typename Base>
25 inline constexpr bool kDerivedFrom =
26 std::is_base_of_v<Base, Derived> &&
27 std::is_convertible_v<std::add_const_t<std::add_volatile_t<Derived>>*,
28 std::add_const_t<std::add_volatile_t<Base>>*>;
29
30 // Converts a pointer or reference between two compatible sibling types: types
31 // that share a common base and with no additional data members. This operation
32 // is NOT recommended in general; this helper is only intended for upstream use.
33 //
34 // A "sibling cast" can be accomplished with a static_cast to the base type
35 // followed by a static_cast to the sibling type. However, this results in
36 // undefined behavior since the cast from the base to the new type is not valid.
37 // This helper ensures that the types are actually compatible and uses
38 // std::launder prevent undefined behavior.
39 //
40 // This function facilitates providing different interfaces for an object
41 // without requiring multiple inheritance. For virtual classes, this reduces
42 // overhead since each virtual base would have its own vtable. For non-virtual
43 // classes, consider instead using multiple private bases to provide alternate
44 // APIs. The derived class holds all data members and returns references to its
45 // private bases to provide different APIs. The bases down cast to the derived
46 // type to access data.
47 template <typename Dest, typename BaseType, typename Source>
SiblingCast(Source && source)48 [[nodiscard]] Dest SiblingCast(Source&& source) {
49 using SourceType = std::remove_pointer_t<std::remove_reference_t<Source>>;
50 using DestType = std::remove_pointer_t<std::remove_reference_t<Dest>>;
51
52 static_assert((std::is_pointer_v<Source> && std::is_pointer_v<Dest>) ||
53 std::is_lvalue_reference_v<Dest> ||
54 std::is_rvalue_reference_v<Dest>,
55 "May only SiblingCast to a pointer or reference type");
56
57 static_assert(std::is_pointer_v<Source> == std::is_pointer_v<Dest>,
58 "Cannot cast between pointer and non-pointer types");
59
60 static_assert(std::is_class_v<BaseType> && !std::is_const_v<BaseType> &&
61 !std::is_volatile_v<BaseType>,
62 "BaseType must be an unqualified class type");
63
64 static_assert(
65 kDerivedFrom<SourceType, BaseType> && kDerivedFrom<DestType, BaseType>,
66 "The source and destination must unambiguously derive from the base");
67
68 static_assert(sizeof(SourceType) == sizeof(BaseType),
69 "The source type cannot add any members to the base");
70 static_assert(sizeof(DestType) == sizeof(BaseType),
71 "The destination type cannot add any members to the base");
72
73 #ifdef __clang__
74 if constexpr (std::is_pointer_v<Source>) {
75 return std::launder(reinterpret_cast<Dest>(std::forward<Source>(source)));
76 } else {
77 Dest dest = reinterpret_cast<Dest>(std::forward<Source>(source));
78 return static_cast<Dest>(*std::launder(std::addressof(dest)));
79 }
80 #else // Alternate implementation for GCC
81 // TODO: b/322910273 - GCC 12 doesn't seem to respect std::launder, resulting
82 // in undesirable optimizations with SiblingCast. Use static_cast for now,
83 // which works as intended, though it is UB.
84
85 // Incrementally add the destination's qualifiers and */&/&& to the base type
86 // for the intermediate static_cast.
87 using Base1 =
88 std::conditional_t<std::is_const_v<DestType>, const BaseType, BaseType>;
89 using Base2 =
90 std::conditional_t<std::is_volatile_v<DestType>, volatile Base1, Base1>;
91 using Base3 = std::conditional_t<std::is_pointer_v<Dest>, Base2*, Base2>;
92 using Base4 =
93 std::conditional_t<std::is_lvalue_reference_v<Dest>, Base3&, Base3>;
94 using Base5 =
95 std::conditional_t<std::is_rvalue_reference_v<Dest>, Base4&&, Base4>;
96
97 return static_cast<Dest>(static_cast<Base5>(source));
98 #endif // __clang__
99 }
100
101 } // namespace pw::internal
102