C++ Logo

SG7

Advanced search

Subject: Re: P2320: "The Syntax of Static Reflection" feedback request
From: David Rector (davrec_at_[hidden])
Date: 2021-02-17 15:23:59


> On Feb 17, 2021, at 1:26 AM, Roland Bock via SG7 <sg7_at_[hidden]> wrote:
>
> On 15.02.21 22:25, Hana Dusíková via SG7 wrote:
>> Hi,
>> Andrew, Wyatt, and Daveed kindly finished the paper which was mentioned during last SG7 session and because the next SG7 meeting is next week *I would like all of you to read the paper and send feedback to SG7 mailing list*, so we have as much of information as possible.
>> The paper is here: https://isocpp.org/files/papers/P2320R0.pdf <https://isocpp.org/files/papers/P2320R0.pdf> and it will be part of the next mailing.
>> Hana
>
>
> Hi all,
>
> The syntax looks looks good to me in general. But I am easy to please, as long as I get the functionality :-)
>
> The real concern I have is that examples are (probably by nature) rather wooden. In the last couple of weeks, I tried to implement std::tuple without inheritance, as a simple struct with data members, i.e.
>
> template<typename... Ts>
> struct tuple
> {
> // ... some operators
>
> // And then data members that are generated as something like
> // This part might require fragments as proposed in P2237R0
> T0 m0;
> T1 m1;
> // ...
> };
>
> I know the current syntax proposal is incomplete, e.g. leaving out identifier splices, but I would like to urge/challenge you to try writing such a tuple as a litmus test.
>

Good example Roland. It is strangely difficult to come up with good, concrete examples, but this is one, and a good opportunity to try out the proposed syntax.

You probably know better than I do but the only way I see to do these is via a) a meta::push_back (or similar) function for adding fragments to a meta::info pack, b) lambdas or private helper functions to generate the expression fragment packs, and c) the `eat` method referenced in P2237 to expand those packs into the proper initializer lists.

Let’s assume for now such a meta::push_back exists, and let’s say idexpr(…) concatenates string literals and integers and returns something which can then be spliced via [::].

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.

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++):] }>;
  }
  
public:
  static const size_t size = sizeof...(Ts);

> In particular, I would like to draw your attention to
>
> a) the following constructors
>
> constexpr tuple(const Ts&... args);

  // 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;
      }() :]... )
  {}

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

>
> 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;
      }() :]... )
  {}

>
> template< class... Us>
> constexpr tuple(const tuple<Us...>& other);

  template<class... Us>
  constexpr tuple(const tuple<Us...>& other)
    : eat( ...[: [](){
        meta::info inits = <()>;
        template for (unsigned I = 0; I < size; ++I)
          meta::push_back(inits, <( [:idexpr("m", I):](std::get<I>(other)) )> );
        return inits;
      }() :]... )
  {}

>
> template <class... Us>
> constexpr tuple(tuple<Us...>&& other);

  template<class... Us>
  constexpr tuple(tuple<Us...>&& other)
    : eat( ...[: [](){
        meta::info inits = <()>;
        template for (unsigned I = 0; I < size; ++I)
          meta::push_back(inits, <( [:idexpr("m", I):](std::move(std::get<I>(other))) )> );
        return inits;
      }() :]... )
  {}

};
```

>
> 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;
  }() :]... );
}

For reference, the "glorified preprocessor" approach to doing this last one, and using $ to splice:

```
template< class... Tuples >
constexpr auto tuple_cat(Tuples&&... tuples) {
  using OutputTuple = typename SomeTypeTraitWhichCatsTheTs<Tuples...>::type;
  consteval {
    << "return OutputTuple(";
    bool first = true;
    for (auto tuplerefl : ^tuples) {
      for (unsigned J = 0, J < decltype($tuplerefl)::size; ++J) {
        if (first) first = false; else << ",";
        << "std::get<" << J << ">(" << $tuplerefl << ")";
      }
    }
    << ");";
  }
}
```

This approach was dismissed in part because the experience with D string mixins suggested it had a tendency to produce unreadable code.

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.

Perhaps the ideal instead should be to give the programmers such powerful capabilities that they can write metaprograms to do the code maintenance for them, and thus never have to reread code :).

> Both tasks look deceivingly simple. And they /should/ be simple with reflection in place, IMHO. Give it a try. How would that look like?
>
> If we cannot write std::tuple nicely(!), I would claim we are doing it wrong.
>
> Just my 2ct.
>
> Thanks,
>
> Roland
>
> PS: As of today, I have an ugly version of tuple_cat but cannot seem to figure out how to write those constructors.
> PPS: Most other stuff is actually quite elegant.
> --
> SG7 mailing list
> SG7_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/sg7



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

Older Archives on Google Groups