I'm not a fan of using magical references to do indirect assignment,
i.e. any of your proposals, but especially `split`. All of these seem
backwards, i.e. should be `tpl1 = head<3>(tpl);` instead.
On 2016-02-12 11:30, Oliver Kowalke wrote:
> 2016-02-12 16:06 GMT+01:00 Matthew Woehlke <mwoehlk...@gmail.com>:
>> I'm not a fan of using magical references to do indirect assignment,
>> i.e. any of your proposals, but especially `split`. All of these seem
>> backwards, i.e. should be `tpl1 = head<3>(tpl);` instead.
>
> I've had std::tie() in mind,
>
> int i...;
> X x...;
> std::tie( i, x) = get_tuple();
That's also a horribly ugly interface that we shouldn't be perpetuating;
http://wg21.link/p0144 solves that exact example ever so much better...
tuple<int, string, string, double> tpl { -1, "abc", "xyz", .5 };
tuple<string, string, double> tpl1;
tpl1 = tail(tpl); // Python: tpl1 = tpl[1:]
tail(tpl) = tpl1; // Python: tpl[1:] = tpl1
std::tuple< int, std::string, std::string, double > tpl{ -1, "abc", "xyz", .5 };
std::tuple< int, std::string, std::string > tpl1;
std::tuple< std::string, double > tpl2;
split( tpl1, tpl2) = tpl;
tuple<int, string, string, double> tpl { -1, "abc", "xyz", .5 };
tuple<int, string> tpl1;
tuple<string, double> tpl2;
std::tie(tpl1~..., tpl2~...) = tpl;
assert(std::get<0>(tpl1) == std::get<0>(tpl));
assert(std::get<1>(tpl1) == std::get<1>(tpl));
assert(std::get<0>(tpl2) == std::get<2>(tpl));
assert(std::get<1>(tpl2) == std::get<3>(tpl));
(Also, I'm assuming that Oliver made some typos in his original post — notice I've flipped the "tpl" and "tpl1" in the second line above.)
Also, of course, head(x) should logically return get<0>(x), not some weird synonym for reverse(tail(reverse(x))).
But that's just poor naming. The underlying operation here is either "splice"
ple [:] require a language modification - but I need a solution now; so I've written head()/tail() (even if the naming is not perfect).
On Friday, February 12, 2016 at 9:11:35 AM UTC-8, Matthew Woehlke wrote:On 2016-02-12 11:30, Oliver Kowalke wrote:
> 2016-02-12 16:06 GMT+01:00 Matthew Woehlke <mwoehlk...@gmail.com>:
>> I'm not a fan of using magical references to do indirect assignment,
>> i.e. any of your proposals, but especially `split`. All of these seem
>> backwards, i.e. should be `tpl1 = head<3>(tpl);` instead.
>
> I've had std::tie() in mind,
>
> int i...;
> X x...;
> std::tie( i, x) = get_tuple();
That's also a horribly ugly interface that we shouldn't be perpetuating;
http://wg21.link/p0144 solves that exact example ever so much better...
I think maybe Oliver was trying to say that
tuple<int, string, string, double> tpl { -1, "abc", "xyz", .5 };
tuple<string, string, double> tpl1;
tpl1 = tail(tpl); // Python: tpl1 = tpl[1:]
tail(tpl) = tpl1; // Python: tpl[1:] = tpl1
should both work, because tail() should return a tuple of references instead of a tuple of values. I definitely support not-making-unnecessary-copies in general, and particularly in this case if it lets tail() work like tie(). But it does seem a little bit non-orthogonal. (Also, I'm assuming that Oliver made some typos in his original post — notice I've flipped the "tpl" and "tpl1" in the second line above.)
Also, of course, head(x) should logically return get<0>(x), not some weird synonym for reverse(tail(reverse(x))). But that's just poor naming. The underlying operation here is either "splice" (extract a contiguous range of indices from the input tuple, as in the Python above) or "select" (extract a noncontiguous set of indices from the input tuple and compress them, as in various vector instruction sets).
I agree that
std::tuple< int, std::string, std::string, double > tpl{ -1, "abc", "xyz", .5 };
std::tuple< int, std::string, std::string > tpl1;
std::tuple< std::string, double > tpl2;
split( tpl1, tpl2) = tpl;
is just nonsense, as written.
However, under the "unpacking with postfix tilde" not-a-proposal, a similar operation could be written like this:
tuple<int, string, string, double> tpl { -1, "abc", "xyz", .5 };
tuple<int, string> tpl1;
tuple<string, double> tpl2;
std::tie(tpl1~..., tpl2~...) = tpl;
assert(std::get<0>(tpl1) == std::get<0>(tpl));
assert(std::get<1>(tpl1) == std::get<1>(tpl));
assert(std::get<0>(tpl2) == std::get<2>(tpl));
assert(std::get<1>(tpl2) == std::get<3>(tpl));
In general, I think that the C++ standard library is woefully lacking in tuple manipulators... but rather than proposing new tuple manipulators (of which there could be a million: zip, fold, map, car, cdr, reverse,...), we should be trying to give people the ability to express those manipulators natively in the language, via something like Matthew's [:] notation or my ~... notation.
016-02-13 1:28 GMT+01:00 Arthur O'Dwyer <arthur....@gmail.com>:
(Also, I'm assuming that Oliver made some typos in his original post — notice I've flipped the "tpl" and "tpl1" in the second line above.)
correct
tail(tpl) = tpl1; // Python:
tpl[1:] = tpl1
Also, of course, head(x) should logically return get<0>(x), not some weird synonym for reverse(tail(reverse(x))).
head(x) should return the part from the source tuple's head that fits into tuple x, same applies to tail(x)
But that's just poor naming. The underlying operation here is either "splice"
I'm only interested in the functionality, not in the naming. One of my project requires such functionality and I was wondering that neither the standard
provides such manipulators nor could I find a proposal.
tuple ~... and tuple [:] require a language modification - but I need a solution now; so I've written head()/tail() (even if the naming is not perfect).
Le 13/02/2016 09:44, Oliver Kowalke a écrit :
I'm confused. Arthur's was not doing an extraction but an insertion in this case016-02-13 1:28 GMT+01:00 Arthur O'Dwyer <arthur....@gmail.com>:
(Also, I'm assuming that Oliver made some typos in his original post — notice I've flipped the "tpl" and "tpl1" in the second line above.)
correct
tail(tpl) = tpl1; // Python: tpl[1:] = tpl1
I believed you were talking of extraction.
I agree that we should first think in terms of the desired functionality and if it is worth having it in the standard. If some additional language features make this easier great.Also, of course, head(x) should logically return get<0>(x), not some weird synonym for reverse(tail(reverse(x))).
head(x) should return the part from the source tuple's head that fits into tuple x, same applies to tail(x)
But that's just poor naming. The underlying operation here is either "splice"
I'm only interested in the functionality, not in the naming. One of my project requires such functionality and I was wondering that neither the standard
provides such manipulators nor could I find a proposal.
tuple ~... and tuple [:] require a language modification - but I need a solution now; so I've written head()/tail() (even if the naming is not perfect).
I believe that you would need to describe more precisely what you want these functions to do.
On 2016–02–13, at 12:30 AM, Oliver Kowalke <oliver....@gmail.com> wrote:I've had std::tie() in mind,
Then proposing a standard library modification is not going to help you
either; in both cases, you'll need to implement a work-around until at
least C++20.
General splicing (i.e. `[:]`) can do the same tasks as your
proposed library features, but it does them *better* (not limited to
std::tuple), and it does *a whole lot more*.
Hi,I'd like to quickly chime in to drop a link to Boost.Hana [1] (which I'm theauthor of, for full disclosure). There seems to be quite a bit of discussionabout adding language features to manipulate packs and tuples, when a lot ofthis could be done in a library. Hana's purpose is specifically to manipulatetuples (and more generally heterogeneous containers) by providing std-likealgorithms to operate on them.Reading the comments here, I just don't see the need for a new languagefeature for manipulating parameter packs. Instead, I think we need properstandard library features to manipulate tuples with a high level ofabstraction.
outer(inner([:]tpl)...)
auto x = inner([:]tpl) + ...;
struct Data
{
int i;
float f;
double d;
};
Data d = ...;
outer(inner([:]d)...);
If properly designed, that could be much more flexible thana language feature in the long term, when we realize that we're missingsomething else. Just to give you a glimpse: how would you reverse a parameterpack? How would you sort a parameter pack based on a compile-time predicate?I don't see how these slicing proposals are of any help, yet this is a veryreal use case for metaprogramming.Instead, I think we need to carefully design a STL for metaprogramming (withcustomization points where it makes sense), and then let users build on topof that. And if you're worried about compile-times being too long with alibrary-based approach, this can be tackled with a few well-chosen compilerintrinsics (see this article [2]).
(FYI, whatever you used to post your last message just butchered the
quoting. And the line wrapping on your own text isn't much better.)
On 2016-02-16 15:21, Louis Dionne wrote:
> On Monday, 15 February 2016 21:10:52 UTC-5, Nicol Bolas wrote:
>> Oh, and show me the Hana code for this:
>>
>> struct Data
>> {
>> int i;
>> float f;
>> double d;
>> };
>>
>> Data d = ...;
>> outer(inner([:]d)...);
>
> Ah! That's a good one! Here's how you would write it:
>
> BOOST_HANA_DEFINE_STRUCT(Data,
Oops. That right there is a no-go. Redefining the type is right out, as
most likely the type is not something that can be directly controlled.
(Even if it was, uglifying the API like this? No, thanks...)
This is where I think we're having a communication issue. The syntax
you're objecting to is mostly related to fold expressions... which *are
already part of the language*.
Please, by all means, submit proposals to make fold expressions /
dealing with parameter packs better :-).
The "meat" of the general unpacking proposal is turning a tuple-like
into a parameter pack. The above suggests that hana doesn't solve that
problem.
auto tuple = std::make_tuple('0', '1', '2', '3', '4', '5');
f(tuple[1, 0, 3, 3]); //equivalent to f('1', '0', '3', '3')
> In that regard, libraries like Fusion and Hana are perfect examples of why> tuple unpacking needs to be a language feature.>> Show me the Hana code for this:>> outer(inner([:]tpl)...)>> This calls `inner` on each element of the tuple, then pipes the result into> the call to `outer`. It works exactly like parameter packs too, so if `tpl`> were a pack, you just drop the `[:]` syntax and it works.With Hana, you'd writehana::unpack(tpl, hana::on(inner, outer));
or you could also writehana::unpack(tpl, [](auto ...x) { return outer(inner(x)...); });
hana::unpack(tpl, [](auto ...x) -> decltype(auto) { return outer(inner(std::forward<decltype(auto)>(x)...)); });
Basically, `unpack` is just `std::apply` but with the arguments reversed.> Show me the Hana code for this:>> auto x = inner([:]tpl) + ...;>> This simply calls a function on each element of the tuple and takes the sum> of the results. Again, it works like parameter packs, so it reuses existing> knowledge.You could writeauto x = hana::fold_left(hana::transform(tpl, inner), std::plus<>{});
or equivalentlyauto x = hana::fold_left(tpl, [](auto a, auto b) {return a + inner(b);});
`hana::fold_left` is just like `std::accumulate`, and `hana::transform` isjust like `std::transform`. To me, the fact that we're using algorithms thatwe already know from runtime programming is a good thing, whereas yourproposed notation requires yet another special thing to learn.
Of course,your proposed syntax wins here because it was designed precisely for theseuse cases, but I hope you'll agree that a library-based solution is nowherenear the ugliness of good old Boost.Lambda expressions.
> Oh, and show me the Hana code for this:>> struct Data> {> int i;> float f;> double d;> };>> Data d = ...;> outer(inner([:]d)...);>> It's the same as the first example, only using an aggregate.Ah! That's a good one! Here's how you would write it:BOOST_HANA_DEFINE_STRUCT(Data,(int, i),(float, f),(double, d));
Data d = ...;hana::unpack(hana::members(d), hana::on(inner, outer));Really, the only cumbersome thing here is the definition of the struct.
On Tuesday, 16 February 2016 16:11:36 UTC-5, Matthew Woehlke wrote:This is where I think we're having a communication issue. The syntax
you're objecting to is mostly related to fold expressions... which *are
already part of the language*.
Please, by all means, submit proposals to make fold expressions /
dealing with parameter packs better :-).
The "meat" of the general unpacking proposal is turning a tuple-like
into a parameter pack. The above suggests that hana doesn't solve that
problem.Did you read my code examples? The above shows that it can be donewith reasonable ease using hana::unpack.
make_tuple(get<[:]index_sequence<1, 0, 3, 3>()>(tpl)...)
I know what a function call looks like. I know what nested function calls look like. `hana::on(inner, outer)` doesn't look anything like that.
Also, I never said that `inner` or `outer` were each a single functions. What happens if they're overloaded? Indeed, given the circumstances (calling `inner` on each type), I imagine that it would be impossible for `inner` to not be overloaded or a template function. Either way, this breaks. Do we need to have lifting syntax for overloads (which to be fair, is a thing we need in general)?
What if `inner` or `outer` is a member function? Does the user now have to whip out `std::bind`?
Also, there are performance questions. Will the indirect call through `hana::on` prevent useful things like inlining?
or you could also writehana::unpack(tpl, [](auto ...x) { return outer(inner(x)...); });
Whenever your library equivalent to a 1-liner language feature includes "introduce a Lambda", you have lost in terms of code comprehensibility. On the other hand, it does fix (most) of the functionality problems outlined above.
But here's the #1 reason why the library solution is the wrong solution: you wrote it wrong. You forgot to `std::forward` your arguments. And you forgot to use `decltype(auto)` for the return value. Both of which are needed to be exactly equivalent to the above code. Without the `decltype(auto)`, if `outer` returned a reference of some kind, it could provoke an unwanted copy.
So it really needed to be:
hana::unpack(tpl, [](auto ...x) -> decltype(auto) { return outer(inner(std::forward<decltype(auto)>(x)...)); });
I fail to see how this could be considered anywhere nearly as easy to understand.
hana::unpack(tpl, [](auto&& ...x) -> decltype(auto) { return outer(inner(std::forward<decltype(x)>(x))...); });
hana::unpack(tpl, [](auto& ...x) -> decltype(auto) { return outer(inner(x)...); });
Basically, `unpack` is just `std::apply` but with the arguments reversed.> Show me the Hana code for this:>> auto x = inner([:]tpl) + ...;>> This simply calls a function on each element of the tuple and takes the sum> of the results. Again, it works like parameter packs, so it reuses existing> knowledge.You could writeauto x = hana::fold_left(hana::transform(tpl, inner), std::plus<>{});
And for people who natively read right to left, this would probably be decent. But that's not how the rest of C++ works.
auto x = ranges::accumulate(ranges::transformed(...), std::plus<>{});
or equivalentlyauto x = hana::fold_left(tpl, [](auto a, auto b) {return a + inner(b);});
Again you forgot to forward the arguments and return values properly. Not to mention, you turn a simple one-liner into a multi-line statement.
`hana::fold_left` is just like `std::accumulate`, and `hana::transform` isjust like `std::transform`. To me, the fact that we're using algorithms thatwe already know from runtime programming is a good thing, whereas yourproposed notation requires yet another special thing to learn.
No, it requires learning exactly one special thing: the syntax to turn a tuple into a parameter pack. After that, you're just using existing parameter pack features. Which you need to know anyway, since that's a feature that we're getting one way or another.
Also, if `fold_left` is "just like `std::accumulate`"... why is it called `fold_left`?
Of course,your proposed syntax wins here because it was designed precisely for theseuse cases, but I hope you'll agree that a library-based solution is nowherenear the ugliness of good old Boost.Lambda expressions.
Actually no. I'd argue that Boost.Lambda is more comprehensible by comparison. While it is certainly extremely bizarre and has a number of pitfalls, once you get used to the idea of a value effectively transforming an expression into a function, it's not so bad for simple cases.
Hana by comparison requires massive effort for the simplest of cases. While I'm sure that it's great for complex metaprogramming, doing something as simple as the cases we've outlined requires a lot of domain knowledge.
That's why I say that Hana would make a good supplement to the language feature. Let the language handle 80+% of all cases; the library can handle the rest.
> Oh, and show me the Hana code for this:>> struct Data> {> int i;> float f;> double d;> };>> Data d = ...;> outer(inner([:]d)...);>> It's the same as the first example, only using an aggregate.Ah! That's a good one! Here's how you would write it:BOOST_HANA_DEFINE_STRUCT(Data,(int, i),(float, f),(double, d));
So... how do I put member functions in `Data`?
struct Data {
BOOST_HANA_DEFINE_STRUCT(Data, (int, i), (float, f),
(double, d)
);
// You can put whatever you want here};
On Tuesday, February 16, 2016 at 6:13:17 PM UTC-5, Louis Dionne wrote:On Tuesday, 16 February 2016 16:11:36 UTC-5, Matthew Woehlke wrote:This is where I think we're having a communication issue. The syntax
you're objecting to is mostly related to fold expressions... which *are
already part of the language*.
Please, by all means, submit proposals to make fold expressions /
dealing with parameter packs better :-).
The "meat" of the general unpacking proposal is turning a tuple-like
into a parameter pack. The above suggests that hana doesn't solve that
problem.Did you read my code examples? The above shows that it can be donewith reasonable ease using hana::unpack.
We have very different ideas about what constitutes "reasonable". Your code is functional, but it is not something I'd like to write or to code review. Reasoning about your code requires in-depth knowledge of what all those functions mean, are, and do.
Reasoning about `...` based code can be complex too; don't get me wrong. But it's only complex in complex cases. In simple cases, it's simple.
Hana is always complicated.
On Tuesday, 16 February 2016 18:19:00 UTC-5, Nicol Bolas wrote:On Tuesday, February 16, 2016 at 3:21:29 PM UTC-5, Louis Dionne wrote:
or you could also writehana::unpack(tpl, [](auto ...x) { return outer(inner(x)...); });
Whenever your library equivalent to a 1-liner language feature includes "introduce a Lambda", you have lost in terms of code comprehensibility. On the other hand, it does fix (most) of the functionality problems outlined above.You're drawing an arbitrary line without any argument for this.
char * strcpy(char *strDest, const char *strSrc)
{
assert(strDest!=NULL && strSrc!=NULL);
char *temp = strDest;
while(*strDest++ = *strSrc++);
return temp;
}
Actually,I tend to prefer writing things more explicitly like above than usingnested ... expansions when things get complex, for I think the differentrules for ... expansion can be confusing.
But here's the #1 reason why the library solution is the wrong solution: you wrote it wrong. You forgot to `std::forward` your arguments. And you forgot to use `decltype(auto)` for the return value. Both of which are needed to be exactly equivalent to the above code. Without the `decltype(auto)`, if `outer` returned a reference of some kind, it could provoke an unwanted copy.
So it really needed to be:
hana::unpack(tpl, [](auto ...x) -> decltype(auto) { return outer(inner(std::forward<decltype(auto)>(x)...)); });
I fail to see how this could be considered anywhere nearly as easy to understand.Fair enough. But if we're going to be pedantic, let's be pedantic for real. What you wantedto write is
hana::unpack(tpl, [](auto&& ...x) -> decltype(auto) { return outer(inner(std::forward<decltype(x)>(x))...); });With this out of the way, I will argue that you actually don't need to write the above, exceptin rare cases where the tuple is potentially a rvalue. Instead, in most cases, you'd justhave to write
hana::unpack(tpl, [](auto& ...x) -> decltype(auto) { return outer(inner(x)...); });which is slightly less verbose.
Basically, `unpack` is just `std::apply` but with the arguments reversed.> Show me the Hana code for this:>> auto x = inner([:]tpl) + ...;>> This simply calls a function on each element of the tuple and takes the sum> of the results. Again, it works like parameter packs, so it reuses existing> knowledge.You could writeauto x = hana::fold_left(hana::transform(tpl, inner), std::plus<>{});
And for people who natively read right to left, this would probably be decent. But that's not how the rest of C++ works.Wtf? How is this different from writing
auto x = ranges::accumulate(ranges::transformed(...), std::plus<>{});
or equivalentlyauto x = hana::fold_left(tpl, [](auto a, auto b) {return a + inner(b);});
Again you forgot to forward the arguments and return values properly. Not to mention, you turn a simple one-liner into a multi-line statement.Same argument as above. And the fact that I broke the statements into multiplelines to make it more readable is a feature. Honestly, I think this (and especiallythe transform/fold_left variant) is more readable than the variant with ... expansions.Of course, your version is more terse, but too terse is not good either.
Let's stop arguing over petty details. You want to hear it? Of course your solutionis better for those use cases, because it was designed with those in mind!But it is also much more limited, and the point I'm trying to make is that fromthe point of view of a metaprogramming library writer, this proposal, in itscurrent form, misses the goal just like fold expressions did.
It's not really that it's terse; that's not what attracts me to tuple expansion. What matters most to me is that the code looks as much like normal code ought to look.
This is also what repels me from your ranges example.
Most programmers know what `outer(inner(value))` does. They can understand that by inspection, and its meaning is clear. Most programmers understand what `inner(value) + inner(value2)` means.
Hana code is not obvious, not to someone who isn't familiar with template metaprogramming and such techniques.
While an unsuspecting programmer may not understand exactly what the `...` and `[:]` parts mean, they can still look at `outer(inner([:]value)...)` and see that `inner` will be called, followed by `outer`. It carries the same physical structure and code layout of the simple and obvious case. It may be more complex under the hood, but the user is not exposed to it.
When looking at `hana::on(inner, outer)`, they have absolutely no idea what that means. Not without looking up the docs. There is no intuitive grasp of what's going on.
The problem it is solving is making tuples work like parameter pack expansion, so as to be able to more effectively access data out of tuples in useful ways.
int, float, double foo();void bar(int, float, double);bar(foo());auto {a, b, c} = foo(); // borrowed from P0144R1
auto tpl = make_tuple(1, 2.f, 3.0);bar(unpack(tpl));bar(unpack<2, 1, 0>(tpl));
template<class... Ts>auto... unpack(tuple<Ts...> x){ return unpack_impl<0>(x, index_sequence_for<Ts...>());}template<size_t... Is, class... Ts>auto... unpack(tuple<Ts...> x){ return unpack_impl<0>(x, make_index_sequence<Is...>());}template<size_t From, size_t To, class... Ts>auto... unpack_range(tuple<Ts...> x){ return unpack_impl<From>(x, make_index_sequence<To - From>);}template<size_t Offset, class... Ts, size_t... Is>auto... unpack_impl(tuple<Ts...> x, index_sequence<Is...>()){ return get<Is + Offset>(x)...;}
template<class Ts...>auto tail(tpl<Ts...> x){ return make_tuple(unpack_range<1, sizeof...(Ts)>(x));}template<size_t Index, class Ts...>auto... split(tpl<Ts...> x){ return make_tuple(unpack<range<0, Index>>(x)), make_tuple(unpack<range<Index, sizeof...(Ts)>>(x));}
I somehow can't shed the feeling that what we really need here is support for multiple return values.
Just imagine for a moment that the following is valid:
int, float, double foo();void bar(int, float, double);bar(foo());auto {a, b, c} = foo(); // borrowed from P0144R1
return 0 1.0 2.0;
auto {a, b, c} = foo();
and so we will need that
auto {a, b, c} = a1, a2, a3;
But
P0144R1 doesn't support it, neither
P0222R0.
Or would the user need to pack it before?
auto {a, b, c} =
make_tuple(foo());
IMO a MRV, or whatever we call the result of a MRV function,
either
* is always unpacked and needs some sort of pack function
(make-tuple) in some cases or
* is packed and we needs some sort of unpack function. I will
not be against the use of an operator to unpack a MRV
bar(*foo());
having this ability makes writing tuple unpacking trivial(and yes, I did omit stuff like rvalue-refs, forward() etc. as they are only distracting and I am sure everyone here can fill in the details themselves)
auto tpl = make_tuple(1, 2.f, 3.0);bar(unpack(tpl));bar(unpack<2, 1, 0>(tpl));
template<class... Ts>auto... unpack(tuple<Ts...> x){return unpack_impl<0>(x, index_sequence_for<Ts...>());}template<size_t... Is, class... Ts>auto... unpack(tuple<Ts...> x){return unpack_impl<0>(x, make_index_sequence<Is...>());}template<size_t From, size_t To, class... Ts>auto... unpack_range(tuple<Ts...> x){return unpack_impl<From>(x, make_index_sequence<To - From>);}template<size_t Offset, class... Ts, size_t... Is>auto... unpack_impl(tuple<Ts...> x, index_sequence<Is...>()){return get<Is + Offset>(x)...;}
return 0 1.0 2.0;
I feel like this opens up the doors to many more possibilities than just the unpacking syntax alone as discussed in this thread. The syntax may not be as terse, but to me "unpack(x)" feels easier to understand and teach than "[:]x". One might consider changing "unpack_range<a, b>(x)" into "unpack<range<a, b>>(x)", making "unpack" even more potent as a commonly understood tool (and allows things like unpack<range<0, 2>, 4>(x) getting the indices 0, 1, 4).
Other examples:
template<class Ts...>auto tail(tpl<Ts...> x){return make_tuple(unpack_range<1, sizeof...(Ts)>(x));}template<size_t Index, class Ts...>auto... split(tpl<Ts...> x){return make_tuple(unpack<range<0, Index>>(x)), make_tuple(unpack<range<Index, sizeof...(Ts)>>(x));}Given the proper handling of lvalue-refs, rvalue-refs, reference_wrapper etc, this should also make the example tail(ref(tpl1)) = tpl2 work.
I know multiple return values come with their own set of problems and since this thread isn't about multiple return values I am not going to address them here.
I guess it comes down to which of the two is more general purpose and/or has the lesser impact on the language. I just felt like mentioning this and maybe get more support for multiple return values if people are interested as the rest emerges naturally from it and the topic of multiple return values resurfaces regularly.
Le 17/02/2016 22:30, Miro Knejp a écrit :
I don't know if mandatory for the subject on this thread, but yes, I believe we need MRV, but I don't know exactly what this would mean yet.I somehow can't shed the feeling that what we really need here is support for multiple return values.
Could you show how would you define foo() to return MRV?Just imagine for a moment that the following is valid:
int, float, double foo();void bar(int, float, double);bar(foo());auto {a, b, c} = foo(); // borrowed from P0144R1
return 0, 1.0, 2.0;
or
return 0 1.0 2.0;
Or?
IIUC, the single things you can do with a MRV is to use it in a call expression and in this case each one of the values is associated to an argument, and
structure binding. However structure binding is associated to a tuple-like interface. Does it means that a MRV thing would have a tuple-like access interface (get<I>(mrv))?
If foo() in the context of bar(foo()) is expanded to bar(a1,a2,a3) for some a1,a2,a3
it should also be expanded in the context of
auto {a, b, c} = foo();
and so we will need that
auto {a, b, c} = a1, a2, a3;
No, since "a1, a2, a3" is a comma-expression and the binding
only applies to the result of the expression "a3". If P0144
doesn't consider this special case then it will become impossible
to add it later. Functions returning multiple values return a
special kind of entity that is transparent to the user and allows
unpacking into arguments and can only exist as a temporary. You
cannot reference it directly, you cannot decltype() it (same as
with parameter packs) and you cannot spell its type. It is not a
different syntax for a comma-expression for which we don't need
anythign new. What we need is special treatment for "this function
returns multiple values and I can pass them as separate arguments
to call expressions". We already have a solution if it's not a
function result but parameters: parameter packs.
But
P0144R1 doesn't support it, neither
P0222R0.
Where is this P0222R0 and why is Google unable to find it? :(
Or would the user need to pack it before?
auto {a, b, c} =
make_tuple(foo());
IMO a MRV, or whatever we call the result of a MRV function,
either
* is always unpacked and needs some sort of pack function
(make-tuple) in some cases or
* is packed and we needs some sort of unpack function. I will
not be against the use of an operator to unpack a MRV
bar(*foo());
My concept treats multiple return values as unpacked, since we
already have solutions for packed values: tuple and other classes.
MRVs can only be used to
(1) be unpacked as parameters to a call expression
(2) be a direct subexpression in a return statement
(3) be unpacked in a fold expression
(4) be captured into variables by the syntax in P0144
I know (4) is redundant because of (1) but I feel like "auto {a,
b, c} = foo()" is simply too convenient to not have it supported
compared to "auto {a, b, c} = std::make_tuple(foo())" (which also
needs an include). With this basic functionality we can implement
everything else mentioned in this thread as a library extension
using make_tuple() and unpack(). But since unpack() doesn't depend
on implementation-defined magical compiler intrinsics anyone can
write their own special snowflake if they so desire.
Unpacking may require the unpack operator to allow the difference
between outer(inner(unpack(x)...)) and outer(inner(unpack(x))...)
as mentioned earlier. That is a detail that needs to be discussed.
Here you introduce something additional. auto... as a deduced MRV.having this ability makes writing tuple unpacking trivial(and yes, I did omit stuff like rvalue-refs, forward() etc. as they are only distracting and I am sure everyone here can fill in the details themselves)
auto tpl = make_tuple(1, 2.f, 3.0);bar(unpack(tpl));bar(unpack<2, 1, 0>(tpl));
template<class... Ts>auto... unpack(tuple<Ts...> x){return unpack_impl<0>(x, index_sequence_for<Ts...>());}template<size_t... Is, class... Ts>auto... unpack(tuple<Ts...> x){return unpack_impl<0>(x, make_index_sequence<Is...>());}template<size_t From, size_t To, class... Ts>auto... unpack_range(tuple<Ts...> x){return unpack_impl<From>(x, make_index_sequence<To - From>);}template<size_t Offset, class... Ts, size_t... Is>auto... unpack_impl(tuple<Ts...> x, index_sequence<Is...>()){return get<Is + Offset>(x)...;}
I suspect to be inline with the way we return here (there is no use of ',' to separate the arguments, the foo() function should return as
return 0 1.0 2.0;
Well, as there is not yet a proposal for MRV we need to consider yours here, to see if is something that could be acceptable. What I mean is that we can not compare two solutions to a specific problem until we have concrete solutions and we can see the advantages and liabilities for both solutions. Maybe you prefer to start a new thread.I feel like this opens up the doors to many more possibilities than just the unpacking syntax alone as discussed in this thread. The syntax may not be as terse, but to me "unpack(x)" feels easier to understand and teach than "[:]x". One might consider changing "unpack_range<a, b>(x)" into "unpack<range<a, b>>(x)", making "unpack" even more potent as a commonly understood tool (and allows things like unpack<range<0, 2>, 4>(x) getting the indices 0, 1, 4).
Other examples:
template<class Ts...>auto tail(tpl<Ts...> x){return make_tuple(unpack_range<1, sizeof...(Ts)>(x));}template<size_t Index, class Ts...>auto... split(tpl<Ts...> x){return make_tuple(unpack<range<0, Index>>(x)), make_tuple(unpack<range<Index, sizeof...(Ts)>>(x));}Given the proper handling of lvalue-refs, rvalue-refs, reference_wrapper etc, this should also make the example tail(ref(tpl1)) = tpl2 work.
I know multiple return values come with their own set of problems and since this thread isn't about multiple return values I am not going to address them here.
I'm not yet for or against any solution, we need concrete proposals containing more details.I guess it comes down to which of the two is more general purpose and/or has the lesser impact on the language. I just felt like mentioning this and maybe get more support for multiple return values if people are interested as the rest emerges naturally from it and the topic of multiple return values resurfaces regularly.
Vicente
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposal...@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
Visit this group at https://groups.google.com/a/isocpp.org/group/std-proposals/.
Am 18.02.2016 um 10:40 schrieb Vicente J. Botet Escriba:
There are examples further down in my post. Besides any syntax presented here is just a strawman so don't get lost in the details too much.Le 17/02/2016 22:30, Miro Knejp a écrit :
I don't know if mandatory for the subject on this thread, but yes, I believe we need MRV, but I don't know exactly what this would mean yet.I somehow can't shed the feeling that what we really need here is support for multiple return values.
Could you show how would you define foo() to return MRV?Just imagine for a moment that the following is valid:
int, float, double foo();void bar(int, float, double);bar(foo());auto {a, b, c} = foo(); // borrowed from P0144R1
return 0, 1.0, 2.0;
or
return 0 1.0 2.0;
Or?
No. If you want to return multiple values in something with a tupe-like interface, well then use tuple and P0144 binding. There's also a proposal in SG14 to synthesize get<>() for public fields, making any old struct compatible with P0144.
IIUC, the single things you can do with a MRV is to use it in a call expression and in this case each one of the values is associated to an argument, and
structure binding. However structure binding is associated to a tuple-like interface. Does it means that a MRV thing would have a tuple-like access interface (get<I>(mrv))?
It is important to keep the multiple return values separate in a special entity to allow for the compiler magic that unpacks it effortlessly into a list of parameters.If foo() in the context of bar(foo()) is expanded to bar(a1,a2,a3) for some a1,a2,a3
it should also be expanded in the context of
auto {a, b, c} = foo();
and so we will need that
auto {a, b, c} = a1, a2, a3;
No, since "a1, a2, a3" is a comma-expression and the binding only applies to the result of the expression "a3". If P0144 doesn't consider this special case then it will become impossible to add it later.
auto {a, b, c} = make_tuple(foo());
Functions returning multiple values return a special kind of entity that is transparent to the user and allows unpacking into arguments and can only exist as a temporary. You cannot reference it directly, you cannot decltype() it (same as with parameter packs) and you cannot spell its type. It is not a different syntax for a comma-expression for which we don't need anythign new. What we need is special treatment for "this function returns multiple values and I can pass them as separate arguments to call expressions". We already have a solution if it's not a function result but parameters: parameter packs.
We need however to know how to return them.
But
P0144R1 doesn't support it, neither P0222R0.
Where is this P0222R0 and why is Google unable to find it? :(
Really sorry it was P0217
https://isocpp.org/blog/2016/02/p0144r1-and-p0217r0
Or would the user need to pack it before?
auto {a, b, c} = make_tuple(foo());
IMO a MRV, or whatever we call the result of a MRV function, either
* is always unpacked and needs some sort of pack function (make-tuple) in some cases or
* is packed and we needs some sort of unpack function. I will not be against the use of an operator to unpack a MRV
bar(*foo());My concept treats multiple return values as unpacked, since we already have solutions for packed values: tuple and other classes. MRVs can only be used to
(1) be unpacked as parameters to a call expression
(2) be a direct subexpression in a return statement
(3) be unpacked in a fold expression
(4) be captured into variables by the syntax in P0144
I'm missing how to build one from N values, bu I see that you
describe it below.
I know (4) is redundant because of (1) but I feel like "auto {a,
b, c} = foo()" is simply too convenient to not have it supported
compared to "auto {a, b, c} = std::make_tuple(foo())" (which
also needs an include).
I will no
t say redundant. If MRV don't provide a syntax
to retrieve the multiple returned values, I would say that there is
something missing.With this basic functionality we can implement everything else mentioned in this thread as a library extension using make_tuple() and unpack(). But since unpack() doesn't depend on implementation-defined magical compiler intrinsics anyone can write their own special snowflake if they so desire.
Unpacking may require the unpack operator to allow the difference between outer(inner(unpack(x)...)) and outer(inner(unpack(x))...) as mentioned earlier. That is a detail that needs to be discussed.
Here you introduce something additional. auto... as a deduced MRV.having this ability makes writing tuple unpacking trivial(and yes, I did omit stuff like rvalue-refs, forward() etc. as they are only distracting and I am sure everyone here can fill in the details themselves)
auto tpl = make_tuple(1, 2.f, 3.0);bar(unpack(tpl));bar(unpack<2, 1, 0>(tpl));
template<class... Ts>auto... unpack(tuple<Ts...> x){return unpack_impl<0>(x, index_sequence_for<Ts...>());}template<size_t... Is, class... Ts>auto... unpack(tuple<Ts...> x){return unpack_impl<0>(x, make_index_sequence<Is...>());}template<size_t From, size_t To, class... Ts>auto... unpack_range(tuple<Ts...> x){return unpack_impl<From>(x, make_index_sequence<To - From>);}template<size_t Offset, class... Ts, size_t... Is>auto... unpack_impl(tuple<Ts...> x, index_sequence<Is...>()){return get<Is + Offset>(x)...;}
I suspect to be inline with the way we return here (there is no use of ',' to separate the arguments, the foo() function should return asRemember this is strawman syntax, but here goes anyway: "auto..." is to tell the compiler that "return a, b, c;" (as is the result of a parameter pack expansion) does not use the comma operator but actually returns multiple values. If the compiler expects only a single value then "return a, b, c;" only returns the result of evaluating "c" (or whatever crazy person overloaded the comma operator with). This is only needed in deduced return types and "auto..." is arbitrarily chosen because it is currently ill-formed. If you spell out all return types akin to something like "int, int, int foo()" then "return a, b, c;" is unambiguous. Both are means to not break existing code.
return 0 1.0 2.0;
I haven't given the standard wording any thought yet so whether MRVs are their own thing or parameter packs or whatever is entirely open for debate (in a different thread if there is enough interest).Well, as there is not yet a proposal for MRV we need to consider yours here, to see if is something that could be acceptable. What I mean is that we can not compare two solutions to a specific problem until we have concrete solutions and we can see the advantages and liabilities for both solutions. Maybe you prefer to start a new thread.
I know multiple return values come with their own set of problems and since this thread isn't about multiple return values I am not going to address them here.
Le 18/02/2016 16:39, Miro Knejp a écrit :
Am 18.02.2016 um 10:40 schrieb Vicente J. Botet Escriba:
There are examples further down in my post. Besides any syntax presented here is just a strawman so don't get lost in the details too much.Le 17/02/2016 22:30, Miro Knejp a écrit :
I don't know if mandatory for the subject on this thread, but yes, I believe we need MRV, but I don't know exactly what this would mean yet.I somehow can't shed the feeling that what we really need here is support for multiple return values.
Could you show how would you define foo() to return MRV?Just imagine for a moment that the following is valid:
int, float, double foo();void bar(int, float, double);bar(foo());auto {a, b, c} = foo(); // borrowed from P0144R1
return 0, 1.0, 2.0;
or
return 0 1.0 2.0;
Or?
As you describe below, these details are important.
Could you give me a reference, because I'm the author of P0197 - Default tuple-like access and I'm really concerned ?No. If you want to return multiple values in something with a tupe-like interface, well then use tuple and P0144 binding. There's also a proposal in SG14 to synthesize get<>() for public fields, making any old struct compatible with P0144.
IIUC, the single things you can do with a MRV is to use it in a call expression and in this case each one of the values is associated to an argument, and
structure binding. However structure binding is associated to a tuple-like interface. Does it means that a MRV thing would have a tuple-like access interface (get<I>(mrv))?
It is important to keep the multiple return values separate in a special entity to allow for the compiler magic that unpacks it effortlessly into a list of parameters.P0144 can not take in account the MRV you are describing here ;-)If foo() in the context of bar(foo()) is expanded to bar(a1,a2,a3) for some a1,a2,a3
it should also be expanded in the context of
auto {a, b, c} = foo();
and so we will need that
auto {a, b, c} = a1, a2, a3;
No, since "a1, a2, a3" is a comma-expression and the binding only applies to the result of the expression "a3". If P0144 doesn't consider this special case then it will become impossible to add it later.
I'm not talking yet of wording, but of how to use the feature.I haven't given the standard wording any thought yet so whether MRVs are their own thing or parameter packs or whatever is entirely open for debate (in a different thread if there is enough interest).Well, as there is not yet a proposal for MRV we need to consider yours here, to see if is something that could be acceptable. What I mean is that we can not compare two solutions to a specific problem until we have concrete solutions and we can see the advantages and liabilities for both solutions. Maybe you prefer to start a new thread.
I know multiple return values come with their own set of problems and since this thread isn't about multiple return values I am not going to address them here.