xref: /aosp_15_r20/external/pigweed/pw_toolchain/public/pw_toolchain/internal/sibling_cast.h (revision 61c4878ac05f98d0ceed94b57d316916de578985)
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