| /* | |
| * Copyright 2020 Google Inc. All rights reserved. | |
| * | |
| * Licensed under the Apache License, Version 2.0 (the "License"); | |
| * you may not use this file except in compliance with the License. | |
| * You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| * See the License for the specific language governing permissions and | |
| * limitations under the License. | |
| */ | |
| // This contains some utilities/examples for how to leverage the static reflec- | |
| // tion features of tables and structs in the C++17 code generation to recur- | |
| // sively produce a string representation of any Flatbuffer table or struct use | |
| // compile-time iteration over the fields. Note that this code is completely | |
| // generic in that it makes no reference to any particular Flatbuffer type. | |
| #include <optional> | |
| #include <string> | |
| #include <type_traits> | |
| #include <utility> | |
| #include <vector> | |
| #include "flatbuffers/flatbuffers.h" | |
| #include "flatbuffers/util.h" | |
| namespace cpp17 { | |
| // User calls this; need to forward declare it since it is called recursively. | |
| template<typename T> | |
| std::optional<std::string> StringifyFlatbufferValue( | |
| T &&val, const std::string &indent = ""); | |
| namespace detail { | |
| /******************************************************************************* | |
| ** Metaprogramming helpers for detecting Flatbuffers Tables, Structs, & Vectors. | |
| *******************************************************************************/ | |
| template<typename FBS, typename = void> | |
| struct is_flatbuffers_table_or_struct : std::false_type {}; | |
| // We know it's a table or struct when it has a Traits subclass. | |
| template<typename FBS> | |
| struct is_flatbuffers_table_or_struct<FBS, std::void_t<typename FBS::Traits>> | |
| : std::true_type {}; | |
| template<typename FBS> | |
| inline constexpr bool is_flatbuffers_table_or_struct_v = | |
| is_flatbuffers_table_or_struct<FBS>::value; | |
| template<typename T> struct is_flatbuffers_vector : std::false_type {}; | |
| template<typename T> | |
| struct is_flatbuffers_vector<flatbuffers::Vector<T>> : std::true_type {}; | |
| template<typename T> | |
| inline constexpr bool is_flatbuffers_vector_v = is_flatbuffers_vector<T>::value; | |
| /******************************************************************************* | |
| ** Compile-time Iteration & Recursive Stringification over Flatbuffers types. | |
| *******************************************************************************/ | |
| template<size_t Index, typename FBS> | |
| std::string AddStringifiedField(const FBS &fbs, const std::string &indent) { | |
| auto value_string = | |
| StringifyFlatbufferValue(fbs.template get_field<Index>(), indent); | |
| if (!value_string) { return ""; } | |
| return indent + FBS::Traits::field_names[Index] + " = " + *value_string + | |
| "\n"; | |
| } | |
| template<typename FBS, size_t... Indexes> | |
| std::string StringifyTableOrStructImpl(const FBS &fbs, | |
| const std::string &indent, | |
| std::index_sequence<Indexes...>) { | |
| // This line is where the compile-time iteration happens! | |
| return (AddStringifiedField<Indexes>(fbs, indent) + ...); | |
| } | |
| template<typename FBS> | |
| std::string StringifyTableOrStruct(const FBS &fbs, const std::string &indent) { | |
| (void)fbs; | |
| (void)indent; | |
| static constexpr size_t field_count = FBS::Traits::fields_number; | |
| std::string out; | |
| if constexpr (field_count > 0) { | |
| out = std::string(FBS::Traits::fully_qualified_name) + "{\n" + | |
| StringifyTableOrStructImpl(fbs, indent + " ", | |
| std::make_index_sequence<field_count>{}) + | |
| indent + '}'; | |
| } | |
| return out; | |
| } | |
| template<typename T> | |
| std::string StringifyVector(const flatbuffers::Vector<T> &vec, | |
| const std::string &indent) { | |
| const auto prologue = indent + std::string(" "); | |
| const auto epilogue = std::string(",\n"); | |
| std::string text; | |
| text += "[\n"; | |
| for (auto it = vec.cbegin(), end = vec.cend(); it != end; ++it) { | |
| text += prologue; | |
| text += StringifyFlatbufferValue(*it).value_or("(field absent)"); | |
| text += epilogue; | |
| } | |
| if (vec.cbegin() != vec.cend()) { | |
| text.resize(text.size() - epilogue.size()); | |
| } | |
| text += '\n' + indent + ']'; | |
| return text; | |
| } | |
| template<typename T> std::string StringifyArithmeticType(T val) { | |
| return flatbuffers::NumToString(val); | |
| } | |
| } // namespace detail | |
| /******************************************************************************* | |
| ** Take any flatbuffer type (table, struct, Vector, int...) and stringify it. | |
| *******************************************************************************/ | |
| template<typename T> | |
| std::optional<std::string> StringifyFlatbufferValue(T &&val, | |
| const std::string &indent) { | |
| (void)indent; | |
| constexpr bool is_pointer = std::is_pointer_v<std::remove_reference_t<T>>; | |
| if constexpr (is_pointer) { | |
| if (val == nullptr) return std::nullopt; // Field is absent. | |
| } | |
| using decayed = | |
| std::decay_t<std::remove_pointer_t<std::remove_reference_t<T>>>; | |
| // Is it a Flatbuffers Table or Struct? | |
| if constexpr (detail::is_flatbuffers_table_or_struct_v<decayed>) { | |
| // We have a nested table or struct; use recursion! | |
| if constexpr (is_pointer) | |
| return detail::StringifyTableOrStruct(*val, indent); | |
| else | |
| return detail::StringifyTableOrStruct(val, indent); | |
| } | |
| // Is it an 8-bit number? If so, print it like an int (not char). | |
| else if constexpr (std::is_same_v<decayed, int8_t> || | |
| std::is_same_v<decayed, uint8_t>) { | |
| return detail::StringifyArithmeticType(static_cast<int>(val)); | |
| } | |
| // Is it an enum? If so, print it like an int, since Flatbuffers doesn't yet | |
| // have type-based reflection for enums, so we can't print the enum's name :( | |
| else if constexpr (std::is_enum_v<decayed>) { | |
| return StringifyFlatbufferValue( | |
| static_cast<std::underlying_type_t<decayed>>(val), indent); | |
| } | |
| // Is it an int, double, float, uint32_t, etc.? | |
| else if constexpr (std::is_arithmetic_v<decayed>) { | |
| return detail::StringifyArithmeticType(val); | |
| } | |
| // Is it a Flatbuffers string? | |
| else if constexpr (std::is_same_v<decayed, flatbuffers::String>) { | |
| return '"' + val->str() + '"'; | |
| } | |
| // Is it a Flatbuffers Vector? | |
| else if constexpr (detail::is_flatbuffers_vector_v<decayed>) { | |
| return detail::StringifyVector(*val, indent); | |
| } | |
| // Is it a void pointer? | |
| else if constexpr (std::is_same_v<decayed, void>) { | |
| // Can't format it. | |
| return std::nullopt; | |
| } | |
| else { | |
| // Not sure how to format this type, whatever it is. | |
| static_assert(sizeof(T) != sizeof(T), | |
| "Do not know how to format this type T (the compiler error " | |
| "should tell you nearby what T is)."); | |
| } | |
| } | |
| } // namespace cpp17 |