xref: /aosp_15_r20/system/media/audio_utils/include/audio_utils/template_utils.h (revision b9df5ad1c9ac98a7fefaac271a55f7ae3db05414)
1 /*
2  * Copyright 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #pragma once
18 
19 #ifdef __cplusplus
20 
21 #include <optional>
22 #include <tuple>
23 #include <type_traits>
24 #include <utility>
25 
26 namespace android::audio_utils {
27 
28 // Determine if a type is a specialization of a templated type
29 // Example: is_specialization_v<T, std::vector>
30 template <typename Test, template <typename...> class Ref>
31 struct is_specialization : std::false_type {};
32 
33 template <template <typename...> class Ref, typename... Args>
34 struct is_specialization<Ref<Args...>, Ref> : std::true_type {};
35 
36 template <typename Test, template <typename...> class Ref>
37 inline constexpr bool is_specialization_v = is_specialization<Test, Ref>::value;
38 
39 // For static assert(false) we need a template version to avoid early failure.
40 template <typename T>
41 inline constexpr bool dependent_false_v = false;
42 
43 // Determine the number of arguments required for structured binding.
44 // See the discussion here and follow the links:
45 // https://isocpp.org/blog/2016/08/cpp17-structured-bindings-convert-struct-to-a-tuple-simple-reflection
46 // Example: is_braces_constructible<T, any_type>()
47 struct any_type {
48   template <class T>
49   constexpr operator T();  // non explicit
50 };
51 
52 template <typename T, typename... TArgs>
53 decltype(void(T{std::declval<TArgs>()...}), std::true_type{})
54 test_is_braces_constructible(int);
55 
56 template <typename, typename...>
57 std::false_type test_is_braces_constructible(...);
58 
59 template <typename T, typename... TArgs>
60 using is_braces_constructible =
61     decltype(test_is_braces_constructible<T, TArgs...>(0));
62 
63 template <typename T, typename... Args>
64 constexpr bool is_braces_constructible_v =
65     is_braces_constructible<T, Args...>::value;
66 
67 // Define a concept to check if a class has a member type `Tag` and `getTag()` method
68 template <typename T>
69 concept has_tag_and_get_tag = requires(T t) {
70     { t.getTag() } -> std::same_as<typename T::Tag>;
71 };
72 
73 template <typename T>
74 inline constexpr bool has_tag_and_get_tag_v = has_tag_and_get_tag<T>;
75 
76 /**
77  * Concept to identify primitive types, includes fundamental types, enums, and
78  * std::string.
79  */
80 template <typename T>
81 concept PrimitiveType = std::is_arithmetic_v<T> || std::is_enum_v<T> ||
82                         std::is_same_v<T, std::string>;
83 
84 // Helper to access elements by runtime index
85 template <typename Tuple, typename Func, size_t... Is>
86 void op_tuple_elements_by_index(Tuple&& tuple, size_t index, Func&& func,
87                                 std::index_sequence<Is...>) {
88   ((index == Is ? func(std::get<Is>(tuple)) : void()), ...);
89 }
90 
91 template <typename Tuple, typename Func>
92 void op_tuple_elements(Tuple&& tuple, size_t index, Func&& func) {
93   constexpr size_t tuple_size = std::tuple_size_v<std::decay_t<Tuple>>;
94   op_tuple_elements_by_index(std::forward<Tuple>(tuple), index,
95                              std::forward<Func>(func),
96                              std::make_index_sequence<tuple_size>{});
97 }
98 
99 /**
100  * The maximum structure members supported in the structure.
101  * If this utility is used for a structure with more than `N` members, the
102  * compiler will fail. In that case, `structure_to_tuple` must be extended.
103  *
104  */
105 constexpr size_t kMaxStructMember = 20;
106 
107 /**
108  * @brief Converts a structure to a tuple.
109  *
110  * This function uses structured bindings to decompose the input structure `t`
111  * into individual elements, and then returns a `std::tuple` containing those
112  * elements.
113  *
114  * Example:
115  * ```cpp
116  * struct Point3D {
117  *     int x;
118  *     int y;
119  *     int z;
120  * };
121  * Point3D point{1, 2, 3};
122  * auto tuple = structure_to_tuple(point);  // tuple will be std::make_tuple(1,
123  * 2, 3)
124  * ```
125  *
126  * @tparam T The type of the structure to be converted.
127  * @param t The structure to be converted to a tuple.
128  * @return A `std::tuple` containing all members of the input structure.
129  *
130  * @note The maximum number of members supported in a structure is
131  * `kMaxStructMember`. If the input structure has more than `kMaxStructMember`
132  * members, a compile-time error will occur.
133  */
134 template <typename T>
135 auto structure_to_tuple(const T& t) {
136   if constexpr (is_braces_constructible<
137                     T, any_type, any_type, any_type, any_type, any_type,
138                     any_type, any_type, any_type, any_type, any_type, any_type,
139                     any_type, any_type, any_type, any_type, any_type, any_type,
140                     any_type, any_type, any_type>()) {
141     auto&& [t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15,
142             t16, t17, t18, t19, t20] = t;
143     return std::make_tuple(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12,
144                            t13, t14, t15, t16, t17, t18, t19, t20);
145   } else if constexpr (is_braces_constructible<
146                            T, any_type, any_type, any_type, any_type, any_type,
147                            any_type, any_type, any_type, any_type, any_type,
148                            any_type, any_type, any_type, any_type, any_type,
149                            any_type, any_type, any_type, any_type>()) {
150     auto&& [t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15,
151             t16, t17, t18, t19] = t;
152     return std::make_tuple(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12,
153                            t13, t14, t15, t16, t17, t18, t19);
154   } else if constexpr (is_braces_constructible<
155                            T, any_type, any_type, any_type, any_type, any_type,
156                            any_type, any_type, any_type, any_type, any_type,
157                            any_type, any_type, any_type, any_type, any_type,
158                            any_type, any_type, any_type>()) {
159     auto&& [t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15,
160             t16, t17, t18] = t;
161     return std::make_tuple(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12,
162                            t13, t14, t15, t16, t17, t18);
163   } else if constexpr (is_braces_constructible<
164                            T, any_type, any_type, any_type, any_type, any_type,
165                            any_type, any_type, any_type, any_type, any_type,
166                            any_type, any_type, any_type, any_type, any_type,
167                            any_type, any_type>()) {
168     auto&& [t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15,
169             t16, t17] = t;
170     return std::make_tuple(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12,
171                            t13, t14, t15, t16, t17);
172   } else if constexpr (is_braces_constructible<
173                            T, any_type, any_type, any_type, any_type, any_type,
174                            any_type, any_type, any_type, any_type, any_type,
175                            any_type, any_type, any_type, any_type, any_type,
176                            any_type>()) {
177     auto&& [t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15,
178             t16] = t;
179     return std::make_tuple(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12,
180                            t13, t14, t15, t16);
181   } else if constexpr (is_braces_constructible<
182                            T, any_type, any_type, any_type, any_type, any_type,
183                            any_type, any_type, any_type, any_type, any_type,
184                            any_type, any_type, any_type, any_type,
185                            any_type>()) {
186     auto&& [t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15] =
187         t;
188     return std::make_tuple(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12,
189                            t13, t14, t15);
190   } else if constexpr (is_braces_constructible<
191                            T, any_type, any_type, any_type, any_type, any_type,
192                            any_type, any_type, any_type, any_type, any_type,
193                            any_type, any_type, any_type, any_type>()) {
194     auto&& [t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14] = t;
195     return std::make_tuple(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12,
196                            t13, t14);
197   } else if constexpr (is_braces_constructible<
198                            T, any_type, any_type, any_type, any_type, any_type,
199                            any_type, any_type, any_type, any_type, any_type,
200                            any_type, any_type, any_type>()) {
201     auto&& [t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13] = t;
202     return std::make_tuple(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12,
203                            t13);
204   } else if constexpr (is_braces_constructible<
205                            T, any_type, any_type, any_type, any_type, any_type,
206                            any_type, any_type, any_type, any_type, any_type,
207                            any_type, any_type>()) {
208     auto&& [t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12] = t;
209     return std::make_tuple(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12);
210   } else if constexpr (is_braces_constructible<T, any_type, any_type, any_type,
211                                                any_type, any_type, any_type,
212                                                any_type, any_type, any_type,
213                                                any_type, any_type>()) {
214     auto&& [t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11] = t;
215     return std::make_tuple(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11);
216   } else if constexpr (is_braces_constructible<T, any_type, any_type, any_type,
217                                                any_type, any_type, any_type,
218                                                any_type, any_type, any_type,
219                                                any_type>()) {
220     auto&& [t1, t2, t3, t4, t5, t6, t7, t8, t9, t10] = t;
221     return std::make_tuple(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10);
222   } else if constexpr (is_braces_constructible<
223                            T, any_type, any_type, any_type, any_type, any_type,
224                            any_type, any_type, any_type, any_type>()) {
225     auto&& [t1, t2, t3, t4, t5, t6, t7, t8, t9] = t;
226     return std::make_tuple(t1, t2, t3, t4, t5, t6, t7, t8, t9);
227   } else if constexpr (is_braces_constructible<T, any_type, any_type, any_type,
228                                                any_type, any_type, any_type,
229                                                any_type, any_type>()) {
230     auto&& [t1, t2, t3, t4, t5, t6, t7, t8] = t;
231     return std::make_tuple(t1, t2, t3, t4, t5, t6, t7, t8);
232   } else if constexpr (is_braces_constructible<T, any_type, any_type, any_type,
233                                                any_type, any_type, any_type,
234                                                any_type>()) {
235     auto&& [t1, t2, t3, t4, t5, t6, t7] = t;
236     return std::make_tuple(t1, t2, t3, t4, t5, t6, t7);
237   } else if constexpr (is_braces_constructible<T, any_type, any_type, any_type,
238                                                any_type, any_type,
239                                                any_type>()) {
240     auto&& [t1, t2, t3, t4, t5, t6] = t;
241     return std::make_tuple(t1, t2, t3, t4, t5, t6);
242   } else if constexpr (is_braces_constructible<T, any_type, any_type, any_type,
243                                                any_type, any_type>()) {
244     auto&& [t1, t2, t3, t4, t5] = t;
245     return std::make_tuple(t1, t2, t3, t4, t5);
246   } else if constexpr (is_braces_constructible<T, any_type, any_type, any_type,
247                                                any_type>()) {
248     auto&& [t1, t2, t3, t4] = t;
249     return std::make_tuple(t1, t2, t3, t4);
250   } else if constexpr (is_braces_constructible<T, any_type, any_type,
251                                                any_type>()) {
252     auto&& [t1, t2, t3] = t;
253     return std::make_tuple(t1, t2, t3);
254   } else if constexpr (is_braces_constructible<T, any_type, any_type>()) {
255     auto&& [t1, t2] = t;
256     return std::make_tuple(t1, t2);
257   } else if constexpr (is_braces_constructible<T, any_type>()) {
258     auto&& [t1] = t;
259     return std::make_tuple(t1);
260   } else {
261     static_assert(false, "Currently supports up to 20 members only.");
262   }
263 }
264 
265 /**
266  * @brief Applies a ternary operation element-wise to structs of type T and
267  * transform the results into a new struct of the same type.
268  *
269  * This function template takes three structs of the same type `T` and an
270  * ternary operation `op`, then applies `op` element-wise to the corresponding
271  * members of the input structs. If all the results of `op` are valid (i.e., not
272  * `std::nullopt`), it constructs a new struct `T` with these results and
273  * returns it as `std::optional<T>`. Otherwise, it returns `std::nullopt`.
274  *
275  * @example
276  * struct Point3D {
277  *   int x;
278  *   int y;
279  *   int z;
280  * };
281  *
282  * std::vector<int> v1{10, 2, 1};
283  * std::vector<int> v2{4, 5, 8};
284  * std::vector<int> v3{1, 8, 6};
285  *
286  * const auto addOp3 = [](const auto& a, const auto& b, const auto& c) {
287  *   return {a + b + c};
288  * };
289  * auto result = op_aggregate(addOp3, v1, v2, v3);
290  * The value of `result` will be std::vector<int>({15, 15, 15})
291  *
292  * @tparam TernaryOp The ternary operation apply to parameters `a`, `b`, and
293  * `c`.
294  * @tparam T The type of `a`, `b`, and `c`.
295  * @param op The ternary operation to apply.
296  * @param a The first input struct.
297  * @param b The second input struct.
298  * @param c The third input struct.
299  * @return A new struct with the aggregated results if all results are valid,
300  *         `std::nullopt` otherwise.
301  */
302 template <typename TernaryOp, typename T, size_t... Is>
303 std::optional<T> op_aggregate_helper(TernaryOp op, const T& a, const T& b,
304                                      const T& c, std::index_sequence<Is...>) {
305   const auto aTuple = structure_to_tuple<T>(a);
306   const auto bTuple = structure_to_tuple<T>(b);
307   const auto cTuple = structure_to_tuple<T>(c);
308 
309   T result;
310   auto resultTuple = structure_to_tuple<T>(result);
311 
312   bool success = true;
313   (
314       [&]() {
315         if (!success) return;  // stop op with any previous error
316         auto val = op(std::get<Is>(aTuple), std::get<Is>(bTuple),
317                       std::get<Is>(cTuple));
318         if (!val) {
319           success = false;
320           return;
321         }
322         std::get<Is>(resultTuple) = *val;
323       }(),
324       ...);
325 
326   if (!success) {
327     return std::nullopt;
328   }
329 
330   return std::apply([](auto&&... args) { return T{args...}; }, resultTuple);
331 }
332 
333 template <typename TernaryOp, typename T>
334 std::optional<T> op_aggregate(TernaryOp op, const T& a, const T& b,
335                               const T& c) {
336   constexpr size_t tuple_size =
337       std::tuple_size_v<std::decay_t<decltype(structure_to_tuple(a))>>;
338   return op_aggregate_helper<TernaryOp, T>(
339       op, a, b, c, std::make_index_sequence<tuple_size>{});
340 }
341 
342 /**
343  * @brief Applies a binary operation element-wise to structs of type T and
344  * transform the results into a new struct of the same type.
345  *
346  * This function template takes three structs of the same type `T` and an
347  * operation `op`, and applies `op` element-wise to the corresponding members of
348  * the input structs. If all the results of `op` are valid (i.e., not
349  * `std::nullopt`), it constructs a new struct `T` with these results and
350  * returns it as `std::optional<T>`. Otherwise, it returns `std::nullopt`.
351  *
352  * @example
353  * struct Point3D {
354  *   int x;
355  *   int y;
356  *   int z;
357  * };
358  *
359  * std::vector<int> v1{10, 2, 1};
360  * std::vector<int> v2{4, 5, 8};
361  * const auto addOp2 = [](const auto& a, const auto& b) {
362  *   return {a + b};
363  * };
364  * auto result = op_aggregate(addOp2, v1, v2);
365  * The value of `result` will be std::vector<int>({14, 7, 9})
366  *
367  * @tparam BinaryOp The binary operation to apply to parameters `a` and `b`.
368  * @tparam T The type of `a` and `b`.
369  * @param op The ternary operation to apply.
370  * @param a The first input struct.
371  * @param b The second input struct.
372  * @return A new struct with the aggregated results if all results are valid,
373  *         `std::nullopt` otherwise.
374  */
375 template <typename BinaryOp, typename T, size_t... Is>
376 std::optional<T> op_aggregate_helper(BinaryOp op, const T& a, const T& b,
377                                      std::index_sequence<Is...>) {
378   const auto aTuple = structure_to_tuple<T>(a);
379   const auto bTuple = structure_to_tuple<T>(b);
380 
381   T result;
382   auto resultTuple = structure_to_tuple<T>(result);
383 
384   bool success = true;
385   (
386       [&]() {
387         if (!success) return;  // stop op with any previous error
388         auto val = op(std::get<Is>(aTuple), std::get<Is>(bTuple));
389         if (!val) {
390           success = false;
391           return;
392         }
393         std::get<Is>(resultTuple) = *val;
394       }(),
395       ...);
396 
397   if (!success) {
398     return std::nullopt;
399   }
400 
401   return std::apply([](auto&&... args) { return T{args...}; }, resultTuple);
402 }
403 
404 template <typename BinaryOp, typename T>
405 std::optional<T> op_aggregate(BinaryOp op, const T& a, const T& b) {
406   constexpr size_t tuple_size =
407       std::tuple_size_v<std::decay_t<decltype(structure_to_tuple(a))>>;
408   return op_aggregate_helper<BinaryOp, T>(
409       op, a, b, std::make_index_sequence<tuple_size>{});
410 }
411 
412 }  // namespace android::audio_utils
413 
414 #endif  // __cplusplus