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