C++ Logo

SG7

Advanced search

Subject: Re: P2320: "The Syntax of Static Reflection" feedback request
From: Andrew Sutton (asutton.list_at_[hidden])
Date: 2021-02-17 16:14:52


>
> Here is then what I would write based on the current proposals, probably
> some mistakes, but let me know if you’re doing something different.
>

Yes.

But first, one thing I note from actually trying this: given the
> expansiveness of the proposed syntax — 4 characters or more needed to
> enclose most entities ([::], <()>, <{}>, <class{ }> etc.) — it now seems a
> bit ridiculous define reflection via a simple ^. Splices seem to be more
> commonly used than reflections (a single reflection often is the source of
> many splice), and so the few times you use ^ it practically becomes hidden
> amidst all those [::] etc., negating the goal of making it stand out. And
> in any case, reflections are not really as substantially different and thus
> in need of visually standing out as splices anyway. So, if using this very
> expansive syntax everywhere else, I say might as well just use reflexpr(…),
> or reflect(…), or even some characters-on-either-side analog of [::] as
> variously suggested by others. (Cue the outraged objections :)
>
> ```
> template<typename... Ts>
> class tuple {
> consteval {
> unsigned I = 0;
> for (auto Trefl : ^Ts)
> << <class { [:Trefl:] [:idexpr("m", I++):] }>;
> }
>

This is close to what generating members might look using injection. Except
that P2237 spells it |# str #| and makes allowances for concatenation.

I prefer Barry's approach for declaring data members in this case (
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p1858r2.html):

template<typename... Ts>
class tuple {
  Ts... elems;
};

I look at this as a special case of what injection could be used for, but
it's also a really good fit for this problem. And concise.

  // Option A: using initializer list
> constexpr tuple(const Ts&... args)
> : eat( ...[: [](){
> meta::info inits = <()>;
> unsigned I = 0;
> template for (auto &arg : args)
> meta::push_back(inits, <( [:idexpr("m", I++):](arg) )> );
> return inits;
> }() :]... )
> {}
>

eat() didn't actually do anything in P2237. It was a hypothetical variadic
function that accepted arguments. If we couldn't do better than this, I
would be deeply disappointed. This should work:

constexpr tuple(T const&... args)
  : ... [: data_members_of(^tuple) :]([:parameters_of(current_function()):])
{ }

Daveed, Wyatt, and I realized we missed splices in member initializer
lists, but this seems sane. We're still mulling over the semantics of pack
expansions involving both ranges and conventional packs. The phrasing here
uses just range splices, so I've left off the postfix expansion (there are
no parameter packs in the pattern).

Barry's proposal would just be this:

constexpr tuple(T const&... args)

  : elems(args)...

{ }

I don't know yet if the pack expansion would be adjusted to also need a
prefix ... since elems not a conventional parameter pack. Again, this is a
nice, concise subset of what we probably do with more verbose
injection/splicing code.

> // Option B: no initializer list
> constexpr tuple(const Ts&... args) {
> unsigned I = 0;
> template for (auto &arg : args)
> << <{ [:idexpr("m", I++):] = arg; }>;
> }
>

eat() is actually useful in this context, but only because I get an
initializer clause. There's probably a better way to do this.

constexpr tuple(T const&... args) {
  eat(...[: data_members_of(^tuple) :] =
[:parameters_of(current_function()):]);
}

A fold expression is tempting but doesn't do the right thing.

  template<typename... Us>
> constexpr tuple(Us&&... args);
>
>
> template<typename… Us>
> constexpr tuple(Us&&... args)
> : eat( ...[: [](){
> meta::info inits = <()>;
> unsigned I = 0;
> template for (auto &arg : args)
> meta::push_back(inits, <( [:idexpr("m", I++):](std::move(arg))
> )> );
> return inits;
> }() :]... )
> {}
>
>
Same as above, but with a move.

  : ... [: data_members_of(^tuple)
:](std::move([:parameters_of(current_function()):]))

I'd worry about generating constraints, but those don't actually involve
the data members. We don't have to do anything novel there.

b) tuple_cat
>
> template< class... Tuples >
> constexpr std::tuple<CTypes...> tuple_cat(Tuples&&... args);
>
>
> ```
> template< class... Tuples >
> constexpr auto tuple_cat(Tuples&&... tuples) {
> using OutputTuple = typename
> SomeTypeTraitWhichCatsTheTs<Tuples...>::type;
> return OutputTuple(eat( ...[: []() {
> meta::info inits = <()>;
> for (auto tuplerefl : ^tuples) {
> for (unsigned J = 0, J < decltype([:tuplerefl:])::size; ++J) {
> meta::push_back(inits, <( std::get<J>( [:tuplerefl:] )> );
> }
> }
> return inits;
> }() :]... );
> }
>

I haven't had an opportunity to think about what this would look like, but
Barry has :)

template <typename... Tuples>
<http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p1858r2.html#cb74-2>constexpr
std::tuple<Tuples.[:]... ...> tuple_cat(Tuples&&... tuples) {
<http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p1858r2.html#cb74-3>
   return {std::forward<Tuples>(tuples).[:]... ...};
<http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p1858r2.html#cb74-4>}

Perhaps the lesson we will learn is that any metaprogramming attempt of
> this scale is going to have a tendency to produce difficult-to-read code.
>

I think you're jumping to conclusions. Metaprogramming code *is* different.
It will take adjustments to learn to read and write effectively, but
concluding that it must be difficult seems premature to me.

Andrew



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

Older Archives on Google Groups