Ben Clayton | 23946b3 | 2023-03-09 16:50:19 +0000 | [diff] [blame] | 1 | // Copyright 2023 The Tint Authors. |
| 2 | // |
| 3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | // you may not use this file except in compliance with the License. |
| 5 | // You may obtain a copy of the License at |
| 6 | // |
| 7 | // http://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, |
| 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | // See the License for the specific language governing permissions and |
| 13 | // limitations under the License. |
| 14 | |
| 15 | #ifndef SRC_TINT_SWITCH_H_ |
| 16 | #define SRC_TINT_SWITCH_H_ |
| 17 | |
| 18 | #include <tuple> |
| 19 | #include <utility> |
| 20 | |
Ben Clayton | 4fea9d0 | 2023-03-09 18:24:19 +0000 | [diff] [blame] | 21 | #include "src/tint/utils/bitcast.h" |
dan sinclair | 12fa303 | 2023-04-19 23:52:33 +0000 | [diff] [blame] | 22 | #include "src/tint/utils/castable.h" |
Ben Clayton | 4fea9d0 | 2023-03-09 18:24:19 +0000 | [diff] [blame] | 23 | #include "src/tint/utils/defer.h" |
Ben Clayton | 23946b3 | 2023-03-09 16:50:19 +0000 | [diff] [blame] | 24 | |
| 25 | namespace tint { |
| 26 | |
| 27 | /// Default can be used as the default case for a Switch(), when all previous cases failed to match. |
| 28 | /// |
| 29 | /// Example: |
| 30 | /// ``` |
| 31 | /// Switch(object, |
| 32 | /// [&](TypeA*) { /* ... */ }, |
| 33 | /// [&](TypeB*) { /* ... */ }, |
| 34 | /// [&](Default) { /* If not TypeA or TypeB */ }); |
| 35 | /// ``` |
| 36 | struct Default {}; |
| 37 | |
| 38 | } // namespace tint |
| 39 | |
| 40 | namespace tint::detail { |
| 41 | |
| 42 | /// Evaluates to the Switch case type being matched by the switch case function `FN`. |
| 43 | /// @note does not handle the Default case |
| 44 | /// @see Switch(). |
| 45 | template <typename FN> |
dan sinclair | 0559005 | 2023-04-19 16:52:46 +0000 | [diff] [blame] | 46 | using SwitchCaseType = |
| 47 | std::remove_pointer_t<utils::traits::ParameterType<std::remove_reference_t<FN>, 0>>; |
Ben Clayton | 23946b3 | 2023-03-09 16:50:19 +0000 | [diff] [blame] | 48 | |
| 49 | /// Evaluates to true if the function `FN` has the signature of a Default case in a Switch(). |
| 50 | /// @see Switch(). |
| 51 | template <typename FN> |
| 52 | inline constexpr bool IsDefaultCase = |
dan sinclair | 0559005 | 2023-04-19 16:52:46 +0000 | [diff] [blame] | 53 | std::is_same_v<utils::traits::ParameterType<std::remove_reference_t<FN>, 0>, Default>; |
Ben Clayton | 23946b3 | 2023-03-09 16:50:19 +0000 | [diff] [blame] | 54 | |
| 55 | /// Searches the list of Switch cases for a Default case, returning the index of the Default case. |
| 56 | /// If the a Default case is not found in the tuple, then -1 is returned. |
| 57 | template <typename TUPLE, std::size_t START_IDX = 0> |
| 58 | constexpr int IndexOfDefaultCase() { |
| 59 | if constexpr (START_IDX < std::tuple_size_v<TUPLE>) { |
| 60 | return IsDefaultCase<std::tuple_element_t<START_IDX, TUPLE>> |
| 61 | ? static_cast<int>(START_IDX) |
| 62 | : IndexOfDefaultCase<TUPLE, START_IDX + 1>(); |
| 63 | } else { |
| 64 | return -1; |
| 65 | } |
| 66 | } |
| 67 | |
Ben Clayton | 23946b3 | 2023-03-09 16:50:19 +0000 | [diff] [blame] | 68 | /// Resolves to T if T is not nullptr_t, otherwise resolves to Ignore. |
| 69 | template <typename T> |
dan sinclair | 12fa303 | 2023-04-19 23:52:33 +0000 | [diff] [blame] | 70 | using NullptrToIgnore = std::conditional_t<std::is_same_v<T, std::nullptr_t>, utils::Ignore, T>; |
Ben Clayton | 23946b3 | 2023-03-09 16:50:19 +0000 | [diff] [blame] | 71 | |
| 72 | /// Resolves to `const TYPE` if any of `CASE_RETURN_TYPES` are const or pointer-to-const, otherwise |
| 73 | /// resolves to TYPE. |
| 74 | template <typename TYPE, typename... CASE_RETURN_TYPES> |
| 75 | using PropagateReturnConst = std::conditional_t< |
| 76 | // Are any of the pointer-stripped types const? |
| 77 | (std::is_const_v<std::remove_pointer_t<CASE_RETURN_TYPES>> || ...), |
| 78 | const TYPE, // Yes: Apply const to TYPE |
| 79 | TYPE>; // No: Passthrough |
| 80 | |
| 81 | /// SwitchReturnTypeImpl is the implementation of SwitchReturnType |
| 82 | template <bool IS_CASTABLE, typename REQUESTED_TYPE, typename... CASE_RETURN_TYPES> |
| 83 | struct SwitchReturnTypeImpl; |
| 84 | |
| 85 | /// SwitchReturnTypeImpl specialization for non-castable case types and an explicitly specified |
| 86 | /// return type. |
| 87 | template <typename REQUESTED_TYPE, typename... CASE_RETURN_TYPES> |
| 88 | struct SwitchReturnTypeImpl</*IS_CASTABLE*/ false, REQUESTED_TYPE, CASE_RETURN_TYPES...> { |
| 89 | /// Resolves to `REQUESTED_TYPE` |
| 90 | using type = REQUESTED_TYPE; |
| 91 | }; |
| 92 | |
| 93 | /// SwitchReturnTypeImpl specialization for non-castable case types and an inferred return type. |
| 94 | template <typename... CASE_RETURN_TYPES> |
dan sinclair | 12fa303 | 2023-04-19 23:52:33 +0000 | [diff] [blame] | 95 | struct SwitchReturnTypeImpl</*IS_CASTABLE*/ false, utils::detail::Infer, CASE_RETURN_TYPES...> { |
Ben Clayton | 23946b3 | 2023-03-09 16:50:19 +0000 | [diff] [blame] | 96 | /// Resolves to the common type for all the cases return types. |
| 97 | using type = std::common_type_t<CASE_RETURN_TYPES...>; |
| 98 | }; |
| 99 | |
| 100 | /// SwitchReturnTypeImpl specialization for castable case types and an explicitly specified return |
| 101 | /// type. |
| 102 | template <typename REQUESTED_TYPE, typename... CASE_RETURN_TYPES> |
| 103 | struct SwitchReturnTypeImpl</*IS_CASTABLE*/ true, REQUESTED_TYPE, CASE_RETURN_TYPES...> { |
| 104 | public: |
| 105 | /// Resolves to `const REQUESTED_TYPE*` or `REQUESTED_TYPE*` |
| 106 | using type = PropagateReturnConst<std::remove_pointer_t<REQUESTED_TYPE>, CASE_RETURN_TYPES...>*; |
| 107 | }; |
| 108 | |
| 109 | /// SwitchReturnTypeImpl specialization for castable case types and an inferred return type. |
| 110 | template <typename... CASE_RETURN_TYPES> |
dan sinclair | 12fa303 | 2023-04-19 23:52:33 +0000 | [diff] [blame] | 111 | struct SwitchReturnTypeImpl</*IS_CASTABLE*/ true, utils::detail::Infer, CASE_RETURN_TYPES...> { |
Ben Clayton | 23946b3 | 2023-03-09 16:50:19 +0000 | [diff] [blame] | 112 | private: |
dan sinclair | 12fa303 | 2023-04-19 23:52:33 +0000 | [diff] [blame] | 113 | using InferredType = utils::CastableCommonBase< |
| 114 | detail::NullptrToIgnore<std::remove_pointer_t<CASE_RETURN_TYPES>>...>; |
Ben Clayton | 23946b3 | 2023-03-09 16:50:19 +0000 | [diff] [blame] | 115 | |
| 116 | public: |
| 117 | /// `const T*` or `T*`, where T is the common base type for all the castable case types. |
| 118 | using type = PropagateReturnConst<InferredType, CASE_RETURN_TYPES...>*; |
| 119 | }; |
| 120 | |
| 121 | /// Resolves to the return type for a Switch() with the requested return type `REQUESTED_TYPE` and |
| 122 | /// case statement return types. If `REQUESTED_TYPE` is Infer then the return type will be inferred |
| 123 | /// from the case return types. |
| 124 | template <typename REQUESTED_TYPE, typename... CASE_RETURN_TYPES> |
| 125 | using SwitchReturnType = typename SwitchReturnTypeImpl< |
dan sinclair | 12fa303 | 2023-04-19 23:52:33 +0000 | [diff] [blame] | 126 | utils::IsCastable<NullptrToIgnore<std::remove_pointer_t<CASE_RETURN_TYPES>>...>, |
Ben Clayton | 23946b3 | 2023-03-09 16:50:19 +0000 | [diff] [blame] | 127 | REQUESTED_TYPE, |
| 128 | CASE_RETURN_TYPES...>::type; |
| 129 | |
| 130 | } // namespace tint::detail |
| 131 | |
| 132 | namespace tint { |
| 133 | |
| 134 | /// Switch is used to dispatch one of the provided callback case handler functions based on the type |
| 135 | /// of `object` and the parameter type of the case handlers. Switch will sequentially check the type |
| 136 | /// of `object` against each of the switch case handler functions, and will invoke the first case |
| 137 | /// handler function which has a parameter type that matches the object type. When a case handler is |
| 138 | /// matched, it will be called with the single argument of `object` cast to the case handler's |
| 139 | /// parameter type. Switch will invoke at most one case handler. Each of the case functions must |
| 140 | /// have the signature `R(T*)` or `R(const T*)`, where `T` is the type matched by that case and `R` |
| 141 | /// is the return type, consistent across all case handlers. |
| 142 | /// |
| 143 | /// An optional default case function with the signature `R(Default)` can be used as the last case. |
| 144 | /// This default case will be called if all previous cases failed to match. |
| 145 | /// |
| 146 | /// If `object` is nullptr and a default case is provided, then the default case will be called. If |
| 147 | /// `object` is nullptr and no default case is provided, then no cases will be called. |
| 148 | /// |
| 149 | /// Example: |
| 150 | /// ``` |
| 151 | /// Switch(object, |
| 152 | /// [&](TypeA*) { /* ... */ }, |
| 153 | /// [&](TypeB*) { /* ... */ }); |
| 154 | /// |
| 155 | /// Switch(object, |
| 156 | /// [&](TypeA*) { /* ... */ }, |
| 157 | /// [&](TypeB*) { /* ... */ }, |
| 158 | /// [&](Default) { /* Called if object is not TypeA or TypeB */ }); |
| 159 | /// ``` |
| 160 | /// |
| 161 | /// @param object the object who's type is used to |
| 162 | /// @param cases the switch cases |
| 163 | /// @return the value returned by the called case. If no cases matched, then the zero value for the |
| 164 | /// consistent case type. |
dan sinclair | 12fa303 | 2023-04-19 23:52:33 +0000 | [diff] [blame] | 165 | template <typename RETURN_TYPE = utils::detail::Infer, |
| 166 | typename T = utils::CastableBase, |
| 167 | typename... CASES> |
Ben Clayton | 23946b3 | 2023-03-09 16:50:19 +0000 | [diff] [blame] | 168 | inline auto Switch(T* object, CASES&&... cases) { |
dan sinclair | 0559005 | 2023-04-19 16:52:46 +0000 | [diff] [blame] | 169 | using ReturnType = detail::SwitchReturnType<RETURN_TYPE, utils::traits::ReturnType<CASES>...>; |
Ben Clayton | 4fea9d0 | 2023-03-09 18:24:19 +0000 | [diff] [blame] | 170 | static constexpr int kDefaultIndex = detail::IndexOfDefaultCase<std::tuple<CASES...>>(); |
| 171 | static constexpr bool kHasDefaultCase = kDefaultIndex >= 0; |
Ben Clayton | 23946b3 | 2023-03-09 16:50:19 +0000 | [diff] [blame] | 172 | static constexpr bool kHasReturnType = !std::is_same_v<ReturnType, void>; |
| 173 | |
Ben Clayton | 4fea9d0 | 2023-03-09 18:24:19 +0000 | [diff] [blame] | 174 | // Static assertions |
| 175 | static constexpr bool kDefaultIsOK = |
| 176 | kDefaultIndex == -1 || kDefaultIndex == static_cast<int>(sizeof...(CASES) - 1); |
| 177 | static constexpr bool kReturnIsOK = |
| 178 | kHasDefaultCase || !kHasReturnType || std::is_constructible_v<ReturnType>; |
| 179 | static_assert(kDefaultIsOK, "Default case must be last in Switch()"); |
| 180 | static_assert(kReturnIsOK, |
| 181 | "Switch() requires either a Default case or a return type that is either void or " |
| 182 | "default-constructable"); |
| 183 | |
| 184 | if (!object) { // Object is nullptr, so no cases can match |
| 185 | if constexpr (kHasDefaultCase) { |
| 186 | // Evaluate default case. |
| 187 | auto&& default_case = |
| 188 | std::get<kDefaultIndex>(std::forward_as_tuple(std::forward<CASES>(cases)...)); |
| 189 | return static_cast<ReturnType>(default_case(Default{})); |
| 190 | } else { |
| 191 | // No default case, no case can match. |
| 192 | if constexpr (kHasReturnType) { |
| 193 | return ReturnType{}; |
| 194 | } else { |
| 195 | return; |
| 196 | } |
| 197 | } |
| 198 | } |
| 199 | |
| 200 | // Replacement for std::aligned_storage as this is broken on earlier versions of MSVC. |
| 201 | using ReturnTypeOrU8 = std::conditional_t<kHasReturnType, ReturnType, uint8_t>; |
| 202 | struct alignas(alignof(ReturnTypeOrU8)) ReturnStorage { |
| 203 | uint8_t data[sizeof(ReturnTypeOrU8)]; |
| 204 | }; |
| 205 | ReturnStorage storage; |
| 206 | auto* result = utils::Bitcast<ReturnTypeOrU8*>(&storage); |
| 207 | |
dan sinclair | 12fa303 | 2023-04-19 23:52:33 +0000 | [diff] [blame] | 208 | const utils::TypeInfo& type_info = object->TypeInfo(); |
Ben Clayton | 4fea9d0 | 2023-03-09 18:24:19 +0000 | [diff] [blame] | 209 | |
| 210 | // Examines the parameter type of the case function. |
| 211 | // If the parameter is a pointer type that `object` is of, or derives from, then that case |
| 212 | // function is called with `object` cast to that type, and `try_case` returns true. |
| 213 | // If the parameter is of type `Default`, then that case function is called and `try_case` |
| 214 | // returns true. |
| 215 | // Otherwise `try_case` returns false. |
| 216 | // If the case function is called and it returns a value, then this is copy constructed to the |
| 217 | // `result` pointer. |
| 218 | auto try_case = [&](auto&& case_fn) { |
| 219 | using CaseFunc = std::decay_t<decltype(case_fn)>; |
| 220 | using CaseType = detail::SwitchCaseType<CaseFunc>; |
Antonio Maiorano | a370186 | 2023-03-14 17:33:34 +0000 | [diff] [blame] | 221 | bool success = false; |
Ben Clayton | 4fea9d0 | 2023-03-09 18:24:19 +0000 | [diff] [blame] | 222 | if constexpr (std::is_same_v<CaseType, Default>) { |
| 223 | if constexpr (kHasReturnType) { |
| 224 | new (result) ReturnType(static_cast<ReturnType>(case_fn(Default{}))); |
| 225 | } else { |
| 226 | case_fn(Default{}); |
| 227 | } |
Antonio Maiorano | a370186 | 2023-03-14 17:33:34 +0000 | [diff] [blame] | 228 | success = true; |
Ben Clayton | 4fea9d0 | 2023-03-09 18:24:19 +0000 | [diff] [blame] | 229 | } else { |
| 230 | if (type_info.Is<CaseType>()) { |
| 231 | auto* v = static_cast<CaseType*>(object); |
| 232 | if constexpr (kHasReturnType) { |
| 233 | new (result) ReturnType(static_cast<ReturnType>(case_fn(v))); |
| 234 | } else { |
| 235 | case_fn(v); |
| 236 | } |
Antonio Maiorano | a370186 | 2023-03-14 17:33:34 +0000 | [diff] [blame] | 237 | success = true; |
Ben Clayton | 4fea9d0 | 2023-03-09 18:24:19 +0000 | [diff] [blame] | 238 | } |
| 239 | } |
Antonio Maiorano | a370186 | 2023-03-14 17:33:34 +0000 | [diff] [blame] | 240 | return success; |
Ben Clayton | 4fea9d0 | 2023-03-09 18:24:19 +0000 | [diff] [blame] | 241 | }; |
| 242 | |
| 243 | // Use a logical-or fold expression to try each of the cases in turn, until one matches the |
| 244 | // object type or a Default is reached. `handled` is true if a case function was called. |
| 245 | bool handled = ((try_case(std::forward<CASES>(cases)) || ...)); |
| 246 | |
Ben Clayton | 23946b3 | 2023-03-09 16:50:19 +0000 | [diff] [blame] | 247 | if constexpr (kHasReturnType) { |
Ben Clayton | 4fea9d0 | 2023-03-09 18:24:19 +0000 | [diff] [blame] | 248 | if constexpr (kHasDefaultCase) { |
| 249 | // Default case means there must be a returned value. |
| 250 | // No need to check handled, no requirement for a zero-initializer of ReturnType. |
| 251 | TINT_DEFER(result->~ReturnType()); |
| 252 | return *result; |
| 253 | } else { |
| 254 | if (handled) { |
| 255 | TINT_DEFER(result->~ReturnType()); |
| 256 | return *result; |
| 257 | } |
| 258 | return ReturnType{}; |
| 259 | } |
Ben Clayton | 23946b3 | 2023-03-09 16:50:19 +0000 | [diff] [blame] | 260 | } |
| 261 | } |
| 262 | |
| 263 | } // namespace tint |
| 264 | |
| 265 | #endif // SRC_TINT_SWITCH_H_ |