Info
-
Did you know that static reflection proposal for C++2X has mirror/value based interface?
Example
template <class T> auto to_string() {
const auto t = get_aliased(mirror(T));
std::stringstream str{};
str << get_name(t) << '{';
for_each(get_enumerators(t),
[&str](auto o) { str << get_name(o) << '=' << get_constant(o) << ';'; }
);
str << '}';
return str.str();
}
enum Weekdays {
Mon = 2,
Tue = 3,
Wed = 4,
Thu = 5,
Fri = 6,
Sat = 1,
Sun = 0
};
int main() {
std::cout << to_string<Weekdays>(); // prints Weekdays{Mon=2;Tue=3;Wed=4;Thu=5;Fri=6;Sat=1;Sun=0;}
}
Puzzle
- Can you implement
to_string
function which returns string representation for given type:type_name{field_type:field_name=value,...}
by applying mirror/value based interface?
template <class T>
auto to_string(const T& t); // TODO
struct empty {};
struct foo {
int i;
double d;
};
struct bar {
foo f;
bool b;
};
int main() {
using namespace boost::ut;
using std::literals::string_literals::operator""s;
"mirror to_string"_test = [] {
expect("empty{}"s == to_string(empty{}));
expect("foo{int:i=1,double:d=0.2}"s == to_string(foo{.i = 1, .d = .2}));
expect("foo{int:i=0,double:d=4.2}"s == to_string(foo{.i = {}, .d = 4.2}));
expect("bar{foo{int:i=2,double:d=0.2},bool:b=1}"s == to_string(bar{.f = {.i = 2, .d = .2}, .b = true}));
};
}
Solutions
template <class T>
auto to_string(const T& t) {
const auto ty = get_aliased(mirror(T));
std::stringstream str{};
str << get_name(ty) << '{';
bool first = true;
for_each(get_data_members(ty),
[&](auto m) {
if (not first) {
str << ',';
} else {
first = false;
}
const auto& val = get_value(m, t);
if constexpr (std::is_class_v<std::remove_cvref_t<decltype(val)>>) {
str << to_string(val);
} else {
str << get_name(get_type(m)) << ':' << get_name(m) << '=' << val;
}
});
str << '}';
return str.str();
}
template <class T>
[[nodiscard]] auto to_string(const T& object) -> std::string {
const auto mirrored_type = get_aliased(mirror(T));
std::stringstream member_stream{};
for_each(get_data_members(mirrored_type), [&](const auto member) -> void {
const auto value = get_value(member, object);
using value_t = std::remove_cvref_t<decltype(value)>;
if constexpr (std::is_class_v<value_t>) { // Why doesn't
// is_class(member) work?
member_stream << to_string(value);
} else {
member_stream << get_name(get_type(member)) << ':'
<< get_name(member) << '=' << value;
}
member_stream << ',';
});
auto member_string = member_stream.str();
if (std::size(member_string) > 0) {
member_string.pop_back();
}
return std::string{get_name(mirrored_type)} + '{' + member_string + '}';
}
template <typename S>
auto to_string(const S& x) {
std::stringstream str{};
str << get_name(get_aliased(mirror(S)));
str << '{';
bool first = true;
for_each(get_data_members(get_aliased(mirror(S))),
[&](auto mo) {
if constexpr(std::is_class_v<std::remove_cvref_t<decltype(get_value(mo,x))>>)
str << to_string(get_value(mo,x)) << ",";
else {
if (first) first = false;
else str << ",";
str << get_name(get_type(mo)) << ":" << get_name(mo) << "=" << get_value(mo, x);
}
});
str << '}';
return str.str();
}
template <class T>
auto to_string(const T& t) {
const auto t_mirror = get_aliased(mirror(T));
std::stringstream str{};
str << get_name(t_mirror) << "{";
std::string sep = "";
for_each(get_data_members(t_mirror),
[&] (auto member) {
auto value = t.*get_pointer(member);
if constexpr (requires {str << value;}) {
str << sep << get_name(get_type(member)) << ":" << get_name(member)
<< "=" << value;
} else {
str << to_string(value);
}
sep = ",";
});
str << "}";
return str.str();
}