xref: /aosp_15_r20/external/flatbuffers/tests/cpp17/stringify_util.h (revision 890232f25432b36107d06881e0a25aaa6b473652)
1 /*
2  * Copyright 2020 Google Inc. All rights reserved.
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 // This contains some utilities/examples for how to leverage the static reflec-
18 // tion features of tables and structs in the C++17 code generation to recur-
19 // sively produce a string representation of any Flatbuffer table or struct use
20 // compile-time iteration over the fields. Note that this code is completely
21 // generic in that it makes no reference to any particular Flatbuffer type.
22 
23 #include <optional>
24 #include <string>
25 #include <type_traits>
26 #include <utility>
27 #include <vector>
28 
29 #include "flatbuffers/flatbuffers.h"
30 #include "flatbuffers/util.h"
31 
32 namespace cpp17 {
33 
34 // User calls this; need to forward declare it since it is called recursively.
35 template<typename T>
36 std::optional<std::string> StringifyFlatbufferValue(
37     T &&val, const std::string &indent = "");
38 
39 namespace detail {
40 
41 /*******************************************************************************
42 ** Metaprogramming helpers for detecting Flatbuffers Tables, Structs, & Vectors.
43 *******************************************************************************/
44 template<typename FBS, typename = void>
45 struct is_flatbuffers_table_or_struct : std::false_type {};
46 
47 // We know it's a table or struct when it has a Traits subclass.
48 template<typename FBS>
49 struct is_flatbuffers_table_or_struct<FBS, std::void_t<typename FBS::Traits>>
50     : std::true_type {};
51 
52 template<typename FBS>
53 inline constexpr bool is_flatbuffers_table_or_struct_v =
54     is_flatbuffers_table_or_struct<FBS>::value;
55 
56 template<typename T> struct is_flatbuffers_vector : std::false_type {};
57 
58 template<typename T>
59 struct is_flatbuffers_vector<flatbuffers::Vector<T>> : std::true_type {};
60 
61 template<typename T>
62 inline constexpr bool is_flatbuffers_vector_v = is_flatbuffers_vector<T>::value;
63 
64 /*******************************************************************************
65 ** Compile-time Iteration & Recursive Stringification over Flatbuffers types.
66 *******************************************************************************/
67 template<size_t Index, typename FBS>
68 std::string AddStringifiedField(const FBS &fbs, const std::string &indent) {
69   auto value_string =
70       StringifyFlatbufferValue(fbs.template get_field<Index>(), indent);
71   if (!value_string) { return ""; }
72   return indent + FBS::Traits::field_names[Index] + " = " + *value_string +
73          "\n";
74 }
75 
76 template<typename FBS, size_t... Indexes>
77 std::string StringifyTableOrStructImpl(const FBS &fbs,
78                                        const std::string &indent,
79                                        std::index_sequence<Indexes...>) {
80   // This line is where the compile-time iteration happens!
81   return (AddStringifiedField<Indexes>(fbs, indent) + ...);
82 }
83 
84 template<typename FBS>
85 std::string StringifyTableOrStruct(const FBS &fbs, const std::string &indent) {
86   (void)fbs;
87   (void)indent;
88   static constexpr size_t field_count = FBS::Traits::fields_number;
89   std::string out;
90   if constexpr (field_count > 0) {
91     out = std::string(FBS::Traits::fully_qualified_name) + "{\n" +
92           StringifyTableOrStructImpl(fbs, indent + "  ",
93                                      std::make_index_sequence<field_count>{}) +
94           indent + '}';
95   }
96   return out;
97 }
98 
99 template<typename T>
100 std::string StringifyVector(const flatbuffers::Vector<T> &vec,
101                             const std::string &indent) {
102   const auto prologue = indent + std::string("  ");
103   const auto epilogue = std::string(",\n");
104   std::string text;
105   text += "[\n";
106   for (auto it = vec.cbegin(), end = vec.cend(); it != end; ++it) {
107     text += prologue;
108     text += StringifyFlatbufferValue(*it).value_or("(field absent)");
109     text += epilogue;
110   }
111   if (vec.cbegin() != vec.cend()) {
112     text.resize(text.size() - epilogue.size());
113   }
114   text += '\n' + indent + ']';
115   return text;
116 }
117 
118 template<typename T> std::string StringifyArithmeticType(T val) {
119   return flatbuffers::NumToString(val);
120 }
121 
122 }  // namespace detail
123 
124 /*******************************************************************************
125 ** Take any flatbuffer type (table, struct, Vector, int...) and stringify it.
126 *******************************************************************************/
127 template<typename T>
128 std::optional<std::string> StringifyFlatbufferValue(T &&val,
129                                                     const std::string &indent) {
130   (void)indent;
131   constexpr bool is_pointer = std::is_pointer_v<std::remove_reference_t<T>>;
132   if constexpr (is_pointer) {
133     if (val == nullptr) return std::nullopt;  // Field is absent.
134   }
135   using decayed =
136       std::decay_t<std::remove_pointer_t<std::remove_reference_t<T>>>;
137 
138   // Is it a Flatbuffers Table or Struct?
139   if constexpr (detail::is_flatbuffers_table_or_struct_v<decayed>) {
140     // We have a nested table or struct; use recursion!
141     if constexpr (is_pointer)
142       return detail::StringifyTableOrStruct(*val, indent);
143     else
144       return detail::StringifyTableOrStruct(val, indent);
145   }
146 
147   // Is it an 8-bit number?  If so, print it like an int (not char).
148   else if constexpr (std::is_same_v<decayed, int8_t> ||
149                      std::is_same_v<decayed, uint8_t>) {
150     return detail::StringifyArithmeticType(static_cast<int>(val));
151   }
152 
153   // Is it an enum? If so, print it like an int, since Flatbuffers doesn't yet
154   // have type-based reflection for enums, so we can't print the enum's name :(
155   else if constexpr (std::is_enum_v<decayed>) {
156     return StringifyFlatbufferValue(
157         static_cast<std::underlying_type_t<decayed>>(val), indent);
158   }
159 
160   // Is it an int, double, float, uint32_t, etc.?
161   else if constexpr (std::is_arithmetic_v<decayed>) {
162     return detail::StringifyArithmeticType(val);
163   }
164 
165   // Is it a Flatbuffers string?
166   else if constexpr (std::is_same_v<decayed, flatbuffers::String>) {
167     return '"' + val->str() + '"';
168   }
169 
170   // Is it a Flatbuffers Vector?
171   else if constexpr (detail::is_flatbuffers_vector_v<decayed>) {
172     return detail::StringifyVector(*val, indent);
173   }
174 
175   // Is it a void pointer?
176   else if constexpr (std::is_same_v<decayed, void>) {
177     // Can't format it.
178     return std::nullopt;
179   }
180 
181   else {
182     // Not sure how to format this type, whatever it is.
183     static_assert(sizeof(T) != sizeof(T),
184                   "Do not know how to format this type T (the compiler error "
185                   "should tell you nearby what T is).");
186   }
187 }
188 
189 }  // namespace cpp17
190