Date: Thu, 18 Feb 2021 08:40:08 +0100
Hi David, hi Andrew,
Thanks both for your replies.
I had somehow missed Barry's paper P1858R2. Thanks for pointing to it.
> template<typename... Ts>
> class tuple {
> Ts... elems;
> };
This is a thing of beauty :-)
The only drawback: I would actually like to be have "real" names for
members. That would allow to use
auto x = t.m1;
instead of
auto x = get<1>(t);
In my view, we should be able to treat tuples as structs and structs as
tuples. We just spell them differently. For instance, std::get would
work with structs. And I could also pass structs into tuple_cat.
> 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 :-)
> I haven't had an opportunity to think about what this would look like, but Barry has :)
>
> template <typename... Tuples>
> constexpr std::tuple<Tuples.[:]... ...> tuple_cat(Tuples&&... tuples) {
> return {std::forward<Tuples>(tuples).[:]... ...};
> }
Thanks again for pointing at Barry's paper. This nested ellipsis would
be great, IMO.
> The tuple_cat example is indeed directly from Mr. Revzin's paper
(P1858R1), and seems purpose built to solve this particular problem.
But what about the next problem? Serious complexity creep seems to be
happening here, and it is not clear where it will end, given that, as
Roland pointed out, the examples so far have been toy examples,
"wooden," not realistic use cases that push the features to the limits.
When the first complicated test case presented must be solved with
new, purpose-built language features…to me, that smells.
There is always a danger of complexity creep in the language. And there
is always the danger of omitting something crucial to prevent complexity
of users' code.
I believe the only way to prevent them is to experiment with the stuff
for real purposes. I am using https://github.com/lock3/meta (currently
on branch new-splice-syntax) to see how https://github.com/rbock/sqlpp11
would look like with reflection and generation in place.
It makes the code orders of magnitude easier to reason about. It might
even allow us to treat containers or streams as databases and use SQL
expressions on them (a bit like LINQ in C#).
And it turns out that constructing SQL statements and result types for
SELECT queries is actually similar to constructing tuples. That's why I
played with them for some time. And it is awesome that we already have
proposals to address that.
So yes, go and find examples that are hard/clumsy without reflection and
start translating them. We need real life examples :-)
Cheers,
Roland
On 17.02.21 23:14, Andrew Sutton via SG7 wrote:
> 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
> <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
>
Thanks both for your replies.
I had somehow missed Barry's paper P1858R2. Thanks for pointing to it.
> template<typename... Ts>
> class tuple {
> Ts... elems;
> };
This is a thing of beauty :-)
The only drawback: I would actually like to be have "real" names for
members. That would allow to use
auto x = t.m1;
instead of
auto x = get<1>(t);
In my view, we should be able to treat tuples as structs and structs as
tuples. We just spell them differently. For instance, std::get would
work with structs. And I could also pass structs into tuple_cat.
> 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 :-)
> I haven't had an opportunity to think about what this would look like, but Barry has :)
>
> template <typename... Tuples>
> constexpr std::tuple<Tuples.[:]... ...> tuple_cat(Tuples&&... tuples) {
> return {std::forward<Tuples>(tuples).[:]... ...};
> }
Thanks again for pointing at Barry's paper. This nested ellipsis would
be great, IMO.
> The tuple_cat example is indeed directly from Mr. Revzin's paper
(P1858R1), and seems purpose built to solve this particular problem.
But what about the next problem? Serious complexity creep seems to be
happening here, and it is not clear where it will end, given that, as
Roland pointed out, the examples so far have been toy examples,
"wooden," not realistic use cases that push the features to the limits.
When the first complicated test case presented must be solved with
new, purpose-built language features…to me, that smells.
There is always a danger of complexity creep in the language. And there
is always the danger of omitting something crucial to prevent complexity
of users' code.
I believe the only way to prevent them is to experiment with the stuff
for real purposes. I am using https://github.com/lock3/meta (currently
on branch new-splice-syntax) to see how https://github.com/rbock/sqlpp11
would look like with reflection and generation in place.
It makes the code orders of magnitude easier to reason about. It might
even allow us to treat containers or streams as databases and use SQL
expressions on them (a bit like LINQ in C#).
And it turns out that constructing SQL statements and result types for
SELECT queries is actually similar to constructing tuples. That's why I
played with them for some time. And it is awesome that we already have
proposals to address that.
So yes, go and find examples that are hard/clumsy without reflection and
start translating them. We need real life examples :-)
Cheers,
Roland
On 17.02.21 23:14, Andrew Sutton via SG7 wrote:
> 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
> <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
>
Received on 2021-02-18 01:40:21