-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.cpp
More file actions
257 lines (224 loc) · 8.49 KB
/
Copy pathmain.cpp
File metadata and controls
257 lines (224 loc) · 8.49 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
#include <iostream>
#include <sstream>
#include <stdexcept>
#include <variant>
#include <fmt/format.h>
#include <fmt/ranges.h>
using namespace std::string_literals;
struct Inner {
int x;
double y;
std::string z;
};
struct Outer {
std::string a;
std::string b;
Inner inner;
};
template <typename T>
struct Simple {
// allows to use the struct to select the formatter directly
// fmt::format("{}", Simple<Outer>{Outer{1,2,Inner{3,4}});
const T& value;
};
template <typename T>
struct Extended {
const T& value;
};
/**
* Structure that holds reflection information about type T.
* @tparam T
*/
template <typename T>
struct reflection {
static constexpr bool available = false;
};
template <typename T>
struct NamedField {
const std::string name;
const T& value;
};
template <>
struct reflection<Outer> {
static constexpr bool available = true;
static auto name() { return "Outer"; }
static auto fields(const Outer& outer) {
return std::tuple{
NamedField<std::string>{"a", outer.a},
NamedField<std::string>{"b", outer.b},
NamedField<Inner>{"inner", outer.inner},
};
}
static auto values(const Outer& outer) { return std::tuple{outer.a, outer.b, outer.inner}; }
};
template <>
struct reflection<Inner> {
static constexpr bool available = true;
static auto name() { return "Inner"; }
static auto fields(const Inner& inner) {
return std::tuple{
NamedField<int>{"x", inner.x},
NamedField<double>{"y", inner.y},
NamedField<std::string>{"z", inner.z},
};
}
static auto values(const Inner& inner) { return std::tuple{inner.x, inner.y, inner.z}; }
};
template <typename T>
struct fmt::formatter<NamedField<T>> {
template <typename FormatContext>
auto format(NamedField<T> const& t, FormatContext& ctx) {
// fixme this is hacky, as we reference the extended format specifier from within the extended formating
// implementation. Would probably be better to visit all name field elements directly in the Extended<T>
// formatter specialization
if constexpr (reflection<T>::available) {
return fmt::format_to(ctx.out(), ".{}={:e}", t.name, t.value);
} else {
return fmt::format_to(ctx.out(), ".{}={}", t.name, t.value);
}
}
};
/**
* Formats objects as concatenated list of their values.
*
* E.g. fmt::format("{:s}", Outer{1,2,Inner{3,4,5}}) will output 1|2|3|4|5
*
* The formatting string may contain a nested string that will be used as delimiter (defaults to "|")
*
* E.g. fmt::format("{:s;}", Outer{1,2,Inner{3,4,5}}) will output 1;2;3;4;5
*
* @tparam T The object to format.
* @tparam C The character type used.
*/
template <typename T, typename C>
struct fmt::formatter<Simple<T>, C, std::enable_if_t<reflection<T>::available, void>> {
template <typename ParseContext>
constexpr auto parse(ParseContext& ctx) {
auto it = ctx.begin(), end = ctx.end();
delimiter = "";
auto out = std::back_inserter(delimiter);
std::copy_if(it, end, out, [](char c) { return c != '}'; });
std::advance(it, delimiter.size());
return it;
}
template <typename FormatContext, typename A>
auto format(A const& a, FormatContext& ctx) {
return format_internal(reflection<A>::values(a), ctx);
}
template <typename FormatContext, typename... Args>
typename FormatContext::iterator format_internal(const std::tuple<Args...>& value, FormatContext& ctx) {
return format_internal(value, ctx, std::make_index_sequence<sizeof...(Args)>{});
}
private:
template <typename FormatContext, typename... Args, size_t... N>
typename FormatContext::iterator format_internal(const std::tuple<Args...>& value, FormatContext& ctx,
internal::index_sequence<N...>) {
return format_args(ctx, std::get<N>(value)...);
}
template <typename FormatContext, typename Arg, typename... Args>
typename FormatContext::iterator format_args(FormatContext& ctx, const Arg& arg, const Args&... args) {
auto out = ctx.out();
if constexpr (reflection<Arg>::available) {
out = format(arg, ctx);
} else {
using base = formatter<typename std::decay<Arg>::type, C>;
out = base{}.format(arg, ctx);
}
if constexpr (sizeof...(Args) > 0) {
out = std::copy(delimiter.begin(), delimiter.end(), out);
ctx.advance_to(out);
return format_args(ctx, args...);
}
return out;
}
std::string delimiter = "|";
};
/**
* Formats objects as they would be written using designated initializers.
*
* E.g. {:e} will output Outer{.a=1, b=2, .inner=Inner{.x=3, .y=4, .z=5}}
*
* @tparam T The object to format.
* @tparam C The character type used.
*/
template <typename T, typename C>
struct fmt::formatter<Extended<T>, C, std::enable_if_t<reflection<T>::available, void>> {
template <typename ParseContext>
constexpr auto parse(ParseContext& ctx) {
auto it = ctx.begin();
if (*it != '}') throw format_error("configuration not yet supported");
return it;
}
template <typename FormatContext>
auto format(T const& t, FormatContext& ctx) {
std::string name = reflection<T>::name();
std::copy(name.begin(), name.end(), ctx.out());
*ctx.out()++ = '{';
using base = formatter<decltype(fmt::join(reflection<T>::fields(t), ", "))>;
base{}.format(fmt::join(reflection<T>::fields(t), ", "), ctx);
*ctx.out()++ = '}';
return ctx.out();
}
};
template <typename T, typename Char>
struct fmt::formatter<T, Char, std::enable_if_t<reflection<T>::available, void>> {
using simple_fmt = fmt::formatter<Simple<T>>;
using extended_fmt = fmt::formatter<Extended<T>>;
using underlying_formatter_type = std::variant<simple_fmt, extended_fmt>;
template <typename ParseContext>
constexpr auto parse(ParseContext& ctx) {
// Parse the presentation format and store it in the formatter:
auto it = ctx.begin(), end = ctx.end();
if (*it == 's') {
underlying_formatter = simple_fmt{};
it++;
} else if (*it == 'e') {
underlying_formatter = extended_fmt{};
it++;
} else {
throw format_error("invalid format");
}
ctx.advance_to(it);
it = std::visit([&ctx](auto& f) { return f.parse(ctx); }, underlying_formatter);
// Check if reached the end of the range:
if (it != end && *it != '}') {
throw format_error("invalid format");
}
// Return an iterator past the end of the parsed range:
return it;
}
template <typename FormatContext>
auto format(T const& t, FormatContext& ctx) {
return std::visit([&](auto& fmt) { return fmt.format(t, ctx); }, underlying_formatter);
}
private:
underlying_formatter_type underlying_formatter;
};
int main() {
auto outer = Outer{.a = "a", .b = "b", .inner = {.x = 1, .y = 3.14, .z = "z"}};
try {
std::stringstream extended;
extended << "Outer{.a=" << outer.a << ", .b=" << outer.b << ", .inner=Inner{.x=" << outer.inner.x
<< ", .y=" << outer.inner.y << ", .z=" << outer.inner.z << "}}";
std::stringstream simple;
simple << outer.a << "|" << outer.b << "|" << outer.inner.x << "|" << outer.inner.y << "|" << outer.inner.z;
std::cout << "Manual extended: " << extended.str() << std::endl;
std::cout << "libfmt extended: " << fmt::format("{:e}", outer) << std::endl;
std::cout << "Manual simple: " << simple.str() << std::endl;
std::cout << "libfmt simple: " << fmt::format("{:s}", outer) << std::endl;
std::cout << "Manual simple: " << simple.str() << std::endl;
std::cout << "libfmt simple: " << fmt::format("{:s;}", outer) << std::endl;
bool extended_fail = fmt::format("{:e}", outer) != extended.str();
bool simple_fail = fmt::format("{:s}", outer) != simple.str();
if (extended_fail) {
std::cerr << "extended format does not match expected output" << std::endl;
}
if (simple_fail) {
std::cerr << "simple format does not match expected output" << std::endl;
}
return simple_fail || extended_fail ? -2 : 0;
} catch (std::exception& e) {
std::cerr << "Error:" << e.what() << std::endl;
return -1;
}
}