C++ Logo


Advanced search

Subject: Re: P2320: "The Syntax of Static Reflection" feedback request
From: David Rector (davrec_at_[hidden])
Date: 2021-02-19 08:34:36

> On Feb 19, 2021, at 1:43 AM, Roland Bock via SG7 <sg7_at_[hidden]> wrote:
> On 18.02.21 22:28, Andrew Sutton wrote:
>> We should be able to make std::get<> work with structs with just reflection:
>> template<std::size_t N, plain_struct T>
>> decltype(auto) get(const T& obj) {
>> return obj.[:data_members_of(^T)[N]:];
>> }
>> That can get a little more complicated when you add inheritance and access specifiers, but not much. Although, now that I think about, we could just replace the usual definition of std::get with something like this, and it automatically works for both tuples and structs.
> Indeed. Ever since I started thinking about reflection, I considered tuple and struct the same thing.
>> A little constexpr if sprinkled in there also makes it work for arrays.
> Excellent idea. Even better :-)
>> I will continue avoiding tuple cat for now :)
>> (Every time I see "tuple cat" or tuple_cat, I keep wanting to read it as "Honky Cat", as in the Elton John song).
> Interesting association, haven't heard that in a while :D
>> > constexpr tuple(T const&... args)
>> > : ... [: data_members_of(^tuple)
>> :]([:parameters_of(current_function()):])
>> > { }
>> While Barry's proposal is much shorter, I would prefer something like
>> this, as it would work with named members :-)
>> The idea of declaring packs vs. naming members is interesting. There are design objectives that motivate both uses, but I don't know if there are obvious criteria that push a design in either direction. Maybe it's just "I want names" vs. "I don't care about names".
> To me having members with actual names is part of the "a tuple is just a struct and vice versa" philosophy.
> And it would sometimes allow to avoid the cognitive overhead of std::get<1>(t).
>> So yes, go and find examples that are hard/clumsy without reflection
>> and
>> start translating them. We need real life examples :-)
>> My experience has been that it is very hard to simultaneously design and implement language features and also dogfood them. It's also more than a bit self-reinforcing. Having other people bring problems --- which you have a great history of :) is excellent fodder for design discussions.
> Fair enough. And I certainly intend to provide more problems while hoping for others to provide the required language feature :-)

I should note here that Roland has been so dedicated and neutral in testing these features that awhile back he not only downloaded and tried an old implementation of mine but even fixed a bug preventing it from building in order to do so. He is deserving of special thanks when this is all over.

Here is another class of non-trivial examples to consider:

template<class T, class U>
class Sum {
  T t;
  U u;
  Sum(T &t, U u) : t(t), u(u) {}
  // Methods: the union of the methods of T and U.
  // Wherever they "share" a method, such that names and
  // signatures of method reflections m_t and m_u are the same
  // (not necc. via inheritance) that method is implemented
  // to return t.[:m:](…) + u.[:m:](…). Otherwise, it returns
  // t.[:m:](…) or u.[:m:](…) individually.

  // Conversion operators: construct from the relevant fields,
  // but for any data T shares with U, need to add in the other’s
  // data to the initializer.
  explicit operator T();
  explicit operator U();

template<class T, class U>
class PreCalcdSum {
  // Data members: union of the "unique" data members
  // of T and U.
  // Initializes each of the data members above, again as either sum
  // from t and u or results from t or u individually
  constexpr PreCalcdSum(T t, U u);
  // Methods: same as before, union of methods of T and U,
  // but now implemented in terms of above-calculated data.

  // Conversion operators: construct from the relevant fields
  explicit operator T();
  explicit operator U();
Next one could try implementing the above not as a pair of T and U but as a tuple of …Ts.

One could also imagine further idiosyncratic logic around the injection of methods — e.g. the names might indicate differentials with respect to other variables, and the user might want to treat those differently in some way, injecting methods which compute integrals for those differentials given additional inputted parameters etc.

The big picture lesson to me: the user really does need to be able to work with ordinary programming facilities to do all this; in particular they need to:
- Be able to come up with their own naming scheme for injected data;
- Initialize those data from a variety of inputted types to constructors;
- Work with ordinary containers (sets, maps, vectors), and
- Inject expressions, initializers etc in a variety of contexts with the help of ordinary control flow statements.

And all the while, the need to keep an eye on just how complexity is being added.

SG7 list run by sg7-owner@lists.isocpp.org

Older Archives on Google Groups