Info
-
Did you know about
__builtin_dump_struct
clang-extension which can nicely print a struct?
Example
#include <cstdint>
#include <cstdio>
#include <utility>
struct trade {
[[no_unique_address]] double price{42.};
[[no_unique_address]] std::size_t size{1'000};
};
int main() {
constexpr auto t = trade{};
__builtin_dump_struct(std::addressof(t), std::addressof(std::printf));
}
const struct trade {
double price : 42.000000
std::size_t size : 1000
}
Puzzle
- Can you implement
to_tuple_with_names
which returnsnamed
fields based on__builtin_dump_struct
input?
template<class T>
struct named {
T value{};
std::string_view name{};
};
template<class T> auto to_tuple_with_names(const T& t); // TODO
int main() {
using namespace boost::ut;
"to tuple with names"_test = [] {
using std::literals::string_view_literals::operator""sv;
should("be empty for empty struct") = [] {
struct empty { };
const auto & t = to_tuple_with_names(empty{});
expect(0_u == std::tuple_size_v<std::remove_cvref_t<decltype(t)>>);
};
should("get value and names for struct with single field") = [] {
struct trade {
std::int32_t price{42};
};
const auto & t = to_tuple_with_names(trade{});
expect(1_u == std::tuple_size_v<std::remove_cvref_t<decltype(t)>>);
expect(42_i == std::get<0>(t).value and "price" == std::get<0>(t).name);
};
should("get value and names for struct with multiple fields") = [] {
struct trade {
std::int32_t price{42};
std::uint32_t quantity{1'000u};
};
const auto & t = to_tuple_with_names(trade{});
expect(2_u == std::tuple_size_v<std::remove_cvref_t<decltype(t)>>);
expect(42_i == std::get<0>(t).value and "price" == std::get<0>(t).name);
expect(1'000_u == std::get<1>(t).value and "quantity" == std::get<1>(t).name);
};
};
}
Solutions
struct any_type {
template<typename T> constexpr operator T() const;
};
template<class T, class ... Args>
constexpr bool is_braces_constructible = requires { T{std::declval<Args>()...};};
char buffer[100];
std::stringstream desc;
int printf_toStream(const char* fmt, ...)
{
va_list args;
va_start(args, fmt);
int count = vsprintf(buffer, fmt, args);
va_end(args);
desc << buffer;
return count;
}
template<class T> auto to_tuple_with_names(const T& t) {
desc = std::stringstream();
__builtin_dump_struct(std::addressof(t), std::addressof(printf_toStream));
std::vector<std::string> lines;
boost::split(lines, desc.str(), boost::is_any_of("\n"));
std::vector<std::string> names;
for(uint32_t i=0; i<lines.size()-3; i++)
{
std::vector<std::string> fields;
boost::split(fields, lines[i+1], boost::is_any_of(" "));
names.push_back(fields[1]);
}
if constexpr( is_braces_constructible<T, any_type, any_type> ) {
auto&& [p0, p1] = t;
return std::make_tuple( named<decltype(p0)>{p0, names[0]}, named<decltype(p1)>{p1, names[1]});
} else if constexpr( is_braces_constructible<T, any_type> ) {
auto&& [p1] = t;
return std::make_tuple( named<decltype(p1)>{p1, names[0]});
} else {
return std::make_tuple();
}
}
struct any_type { template<class T> constexpr operator T(); };
template<class T>
auto to_tuple_with_names(const T& t) {
static std::vector<std::string> names{};
names = {};
struct set {
static auto ns(const char* str, ...) -> int {
std::string type_name{str};
if (type_name.contains(" : ")) {
type_name = type_name.substr(type_name.find(' ') + 1);
names.push_back(type_name.substr(0, type_name.find(':') - 1));
}
return {};
}
};
__builtin_dump_struct(std::addressof(t), std::addressof(set::ns));
if constexpr(requires { T{any_type{}, any_type{}}; }) {
auto [p1, p2] = t;
assert(2 == std::size(names));
return std::tuple(named<decltype(p1)>{.value = p1, .name = names[0]}, named<decltype(p2)>{.value = p2, .name = names[1]});
} else if constexpr(requires { T{any_type{}}; }) {
auto [p1] = t;
assert(1 == std::size(names));
return std::tuple(named<decltype(p1)>{.value = p1, .name = names[0]});
} else {
assert(std::empty(names));
return std::tuple{};
}
}
struct universal_arg { template<typename T> constexpr operator T() const; };
template <class T>
concept one_field = requires { T{universal_arg{}}; };
template <class T>
concept two_fields = requires { T{universal_arg{}, universal_arg{}}; };
static std::vector<std::string> names;
int printf_to_buffer(const char* fmt, ...) {
va_list args;
va_start(args, fmt);
char buffer[256];
const auto ret = vsprintf(buffer, fmt, args);
va_end(args);
if (const auto v = std::string_view{buffer}; v.contains(':')) {
if (const auto name_start = v.find(' '); name_start != std::string_view::npos) {
const auto name_end = v.find(' ', name_start + 1);
names.emplace_back(&v[name_start + 1], &v[name_end]);
}
}
return ret;
}
template <class T> auto to_tuple_with_names(const T& t) {
names.clear();
__builtin_dump_struct(std::addressof(t), &printf_to_buffer);
if constexpr (two_fields<T>) {
auto &[f0, f1] = t;
return std::tuple{named{f0, names[0]}, named{f1, names[1]}};
} else if constexpr (one_field<T>) {
auto &[f0] = t;
return std::tuple{named{f0, names[0]}};
} else {
return std::tuple{};
}
}
template<typename T>
struct Reflector
{
#define MAX_NUM_FIELDS 255
struct Any{
Any(int){}
template<typename MT> constexpr operator MT(){return MT{};}
};
template<int N>
static consteval bool Initializeable( )
{
return []<int ... Is > ( std::integer_sequence<int, Is...> const & )
{
return (requires{ T { Any(Is) ... };});
}( std::make_integer_sequence<int,N>());
}
template<int N = MAX_NUM_FIELDS > //search down from MAX_NUM_FIELDS
static consteval int NumFields()
{
if constexpr ( Initializeable<N>())
{
return N;
} else
return NumFields<N-1>();
}
template<int N = NumFields()>
static auto ToTuple( auto && t )
{
#define ELEMENTS(z,n,text) BOOST_PP_COMMA_IF( n ) BOOST_PP_CAT(text,n)
#define OP(r, I) BOOST_PP_DEC(I)
#define PRED(r, I) BOOST_PP_NOT_EQUAL(I, 0)
#define TUPLE_N( r,I ) \
if constexpr ( N == I ) { auto [BOOST_PP_REPEAT(I, ELEMENTS, a)] = t;return std::make_tuple(BOOST_PP_REPEAT(I, ELEMENTS, a));}
BOOST_PP_FOR(MAX_NUM_FIELDS, PRED, OP, TUPLE_N)
if constexpr( N == 0 ) return std::make_tuple();
}
};
template< typename T >
struct NameParser{
static std::vector<std::string> names;
static int parse(const char* format,... )
{
int n = 0;
int p1= 0;
int p2= 0;
while (format[n]!='\0' && format[n+1]!='\0')
{
if( format[n] == ' ')
{
if( p1 == 0 )
p1 = n;
else
p2 = n ;
}
if( format[n] ==':' && format[n+1] ==' ' )
{
auto & name = names.emplace_back(p2-p1-1,'0');
for( int i = p1 + 1 ; i < p2; ++i )
name[i-p1 - 1] = format[i];
}
++n;
}
return 0;
}
};
template <typename T> std::vector<std::string> NameParser<T>::names = {};
template<typename T>
struct NamedValue
{
NamedValue(std::string const & name, T const & value ): name(name),value(value){}
std::string name;
T value;
};
template<class T> auto to_tuple_with_names(const T& t)
{
__builtin_dump_struct(std::addressof(t), &NameParser<T>::parse);
auto tp = Reflector<T>::ToTuple(t);
return [&]<int ... IS>( std::integer_sequence<int, IS...> const & )
{
return std::make_tuple( NamedValue( NameParser<T>::names[IS], std::get<IS>(tp)) ... );
}( std::make_integer_sequence<int,std::tuple_size_v<std::remove_cvref_t<decltype(tp)>>>() );
}
namespace detail {
template <typename...>
struct always_false : std::false_type {};
template <auto>
struct any_type {
template<typename T> constexpr operator T() const;
};
template <class T, std::size_t... Ns>
constexpr bool has_n_members_impl(std::index_sequence<Ns...>) {
return requires { T{any_type<Ns>{}...}; };
}
template <class T, std::size_t N>
constexpr bool has_n_members = has_n_members_impl<T>(std::make_index_sequence<N>{});
} // namespace detail
struct to_tuple_with_names_impl {
inline static std::vector<std::string> names{};
static int dump_struct_helper(const char* fmt, ...) {
if (const std::string_view dump{fmt}; dump.contains(':')) {
const auto name_begin = dump.find(' ') + 1;
const auto name_end = dump.find(' ', name_begin);
names.emplace_back(&dump[name_begin], &dump[name_end]);
}
return {};
}
template <typename T>
auto operator()(const T& t) const {
names.clear();
__builtin_dump_struct(std::addressof(t), &dump_struct_helper);
if constexpr (detail::has_n_members<T, 2>) {
const auto& [a, b] = t;
return std::tuple {
named {a, names[0]},
named {b, names[1]}
};
} else if constexpr (detail::has_n_members<T, 1>) {
const auto& [a] = t;
return std::tuple {
named {a, names[0]}
};
} else if constexpr (detail::has_n_members<T, 0>) {
return std::tuple{};
} else {
static_assert(detail::always_false<T>::value, "struct is too large");
}
}
};
template<class T> [[nodiscard]] auto to_tuple_with_names(const T& t) {
return to_tuple_with_names_impl{}(t);
}