James Kuszmaul | 8e62b02 | 2022-03-22 09:33:25 -0700 | [diff] [blame^] | 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 |