...
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
32namespace cpp17 {
33
34// User calls this; need to forward declare it since it is called recursively.
35template<typename T>
36std::optional<std::string> StringifyFlatbufferValue(
37 T &&val, const std::string &indent = "");
38
39namespace detail {
40
41/*******************************************************************************
42** Metaprogramming helpers for detecting Flatbuffers Tables, Structs, & Vectors.
43*******************************************************************************/
44template<typename FBS, typename = void>
45struct is_flatbuffers_table_or_struct : std::false_type {};
46
47// We know it's a table or struct when it has a Traits subclass.
48template<typename FBS>
49struct is_flatbuffers_table_or_struct<FBS, std::void_t<typename FBS::Traits>>
50 : std::true_type {};
51
52template<typename FBS>
53inline constexpr bool is_flatbuffers_table_or_struct_v =
54 is_flatbuffers_table_or_struct<FBS>::value;
55
56template<typename T> struct is_flatbuffers_vector : std::false_type {};
57
58template<typename T>
59struct is_flatbuffers_vector<flatbuffers::Vector<T>> : std::true_type {};
60
61template<typename T>
62inline constexpr bool is_flatbuffers_vector_v = is_flatbuffers_vector<T>::value;
63
64/*******************************************************************************
65** Compile-time Iteration & Recursive Stringification over Flatbuffers types.
66*******************************************************************************/
67template<size_t Index, typename FBS>
68std::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
76template<typename FBS, size_t... Indexes>
77std::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
84template<typename FBS>
85std::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
99template<typename T>
100std::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
118template<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*******************************************************************************/
127template<typename T>
128std::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
View as plain text