Implementing the Tuple API easily using custom annotations and reflections
Hey, so I've been working on something interesting lately. It's a solution to a problem that you may have encountered in one form or anothing but it boils down to this: you have a struct:
struct mystruct
{
long a;
int b;
char c;
};
And you want this to work:
auto [a, b, c] = instance;
Well it doesn't, aggregates don't implement the tuple API required for structured bindings by default and implementing it manually is a pain. You need to write your own specialization of std::tuple\size, std::tuple_element and implement a visible get<> method. It might not be that much work at first glance but it becomes very tiresome if you have multiple such structs or if you try to deviate from the general default indexing and not to mention a paint to maintain and update.
Well, why write repeated code when you can only write code once that writes that code for you? Reflections are mature enough in gcc's implementation to solve this. Simply put I have compiling code that with only the application of an annotation gives me all of this:
struct [=tuple_like] simpletuple
{
long a;
int b;
char c;
};
Through which I get:
staticassert(std::tuplesizev<simpletuple> == 3);
constexpr simpletuple simple { 1, 2, 3 };
staticassert(1 == get<0>(simple));
staticassert(2 == get<1>(simple));
staticassert(3 == get<2>(simple));
staticassert(std::sameas<std::tupleelementt<0, simpletuple>, long>);
staticassert(std::sameas<std::tupleelementt<1, simpletuple>, int>);
staticassert(std::sameas<std::tupleelementt<2, simpletuple>, char>);
And of couse:
auto a, b, c = simple;
It doesn't stop here though, since the case I described above is common but inflexible. Customization is available and an opt-in feature:
struct [=tuple_like] customizabletuple
{
constexpr customizabletuple(long a, const char b, float c, char d, double e)
: a(a)
, b(b)
, c(c)
, d(d)
, e(e)
{}
[[=tuple_element(0)]]
long a;
[[=read_only_tuple_element(3)]]
const char b;
[=tuple_element(2)]
float c;
char d;
private:
[=read_only_tuple_element(1)]
double e;
};
It supports:
tuple element index reordering
implicit data member exclusion from the API
explicit read-only modifier
explicit opt-in for private fields
correct semantics for members which are reference types
​
constexpr customizable_tuple custom( 1, "2", 3, 4, 5 );
static_assert(std::tuple_size_v<customizable_tuple> == 4);
static_assert(1 == get<0>(custom));
static_assert(std::string_view{"2"} == get<3>(custom));
static_assert(3 == get<2>(custom));
static_assert(5 == get<1>(custom));
static_assert(std::same_as<std::tuple_element_t<0, customizable_tuple>, long>);
static_assert(std::same_as<std::tuple_element_t<1, customizable_tuple>, const double>);
static_assert(std::same_as<std::tuple_element_t<2, customizable_tuple>, float>);
static_assert(std::same_as<std::tuple_element_t<3, customizable_tuple>, const char const>);
But it doesn't stop there, no it doesn't. The part that took the most effort to implement were the error messages. No, we have better static_assert's in C++26 now, we abuse them:
Example 1:
staticassert(3 == get<3>(simple));
/app/myreflectutils.hpp:819:17:
error:
static assertion failed: reflectutils: get<3> called for tuple-like type 'simpletuple', but its reflected tuple size is 3.
Example 2:
[[=tupleelement(1)]]
long a;
[=tuple_element(2)]
int b;
[=tuple_element(3)]
char c;
/app/myreflectutils.hpp:632:21: error: static assertion failed: reflectutils: invalid tuple layout for 'simpletuple': tuple index 0 is missing. There are 3
Post #25684
13