std::tuple<int,float,double> foo();
int x;
double d;
std::tie(x, std::ignore, d) = foo();
int x;
[x; void; auto d] = foo();
int x;
auto _t = foo();
x = std::get<0>(std::move(_t));
auto d = std::get<2>(std::move(_t));
[auto x] = foo(); //same as [auto x, void, void] = foo()
int x;
(x; void; auto d) = foo();
int x;
auto (x; void; auto d) = foo();
[int,float,double] foo()
{
return [5; 23.4f; 10447.023d];
}
double v{};
[auto x, float g, v] = foo();
On Thursday, May 21, 2015 at 3:49:44 AM UTC-4, Morwenn wrote:
While I don't really like the syntax (it's hard to make something short when you have to declare a type anyway), I want to say that I really much like the use of std::get to get the parameters. That means that any type compatible with std::get, such as std::pair or std::array, could be used for multi-valued return types. This is IMO better than forcing users to use std::tuple everywhere and make some standard library functions returning std::pair work with the syntax out of the box.
I'd also like to see this feature added to c++. I don't really like the proposed syntax either (the square brackets are out-of-place here, imo). The best would be to simply use
int x;
(x; void; auto d) = foo();
but I guess that'd introduce parsing issues. How about:
int x;
auto (x; void; auto d) = foo();
int x;
int y;
auto (x; y; void) = foo();
On 2015-05-20 21:14, Matthew Fioravante wrote:
> I've chosen to use [] instead of {} [...]
Given the syntax comments, I would encourage you to read the previous
thread(s). That said, I have to say I'm actually in favor of the []
syntax; it "feels" least likely to have parsing issues, and [] normally
being indexing operators, it makes sense to me to use them for
unpacking. (It probably also helps that I write the odd bit of Python,
and Python uses [] to write lists :-).)
> I've chosen to use ; instead of , to avoid confusion with the comma
> operator.
I strongly agree with using ';' to separate elements, since multiple
declarations should be permitted, but '[auto x, auto y]' clearly makes
no syntactic sense. That said, is ',' allowed inside the []'s at all? If
yes, what does it mean?
[auto x, y] = bar(); // forces decltype(x) == decltype(y)?
auto t = bar();
auto x, y = std::get<0>(t);
[int x, y;void;void] = foo();
int& operator,(int& a, int& b) { return a; }
int x;
int y;
[ x,y; void; void] = foo(); //assignes std::get<0>(foo()) to x
> We can also allow the user to specify a shorter list, with default values
> of void for the trailing elements not mentioned:
>
> [auto x] = foo(); //same as [auto x, void, void] = foo()
I'd be somewhat strongly inclined to follow Python, and not discard any
values. So the last item in the list becomes a tuple of whatever is left
over.
So, to take only the first value, you would write:
[auto x; void] = foo();
I also wonder if it should be an error if the number of arguments don't
match, and require '...' to allow an item to expand to a (partial)
tuple. For example:
// assume foo() -> tuple<int, double, int>
[auto x; void...] = foo(); // like above
[auto x; auto... y] = foo(); // decltype(y) == tuple<double, int>
tuple<int,int> foo();
[auto x; auto y; auto... z] = foo();
//decltype(z) is tuple<>.
tuple<int,int> a();
array<int,2> b();
pair<int,int> c();
[auto xa; auto xva] = a(); //decltype(xva) is tuple<int>?
[auto xb; auto xvb] = b(); //decltype(xvb) is array<int,1>?
[auto xc; auto xvc] = c(); //What is decltype(xvc)???
[auto first; auto second; auto... tail] = foo();
use(first);
auto params = concat({ first }, tail)
bar(params)
The down side of course is that this (except the 'void...' case)
requires the compiler to know how many items are in the result. For
tuple this is probably easy. For "generic" types it may be harder, but I
imagine if necessary we could introduce a companion to std::get to
return the largest index that can be used with std::get, if we don't
already have ways to accomplish that.
> What do you think about such a feature?
As you may recall from (at least one of) the time(s) this came up
previously, I am in favor of this :-).
This may be implied anyway, but I'll say it explicitly; let's please
also support this in range-based for:
for ([auto key; auto value] : some_map) { ... }
If we're going to have special syntax for using multiple return values, I would much rather we have actual multiple return value syntax, not something built on `tuple`.
Using the `[]` syntax noted here, you could have matching return and capture:
[int,float,double] foo()
{
return [5; 23.4f; 10447.023d];
}
double v{};
[auto x, float g, v] = foo();
[int,float,double] foo()
{
return {5, 23.4f, 10447.023d};
}
double v{};
[auto x; float g; v] = foo();
The reason to want this to be a legitimate language feature is so that we can have all of the aspects of return values still apply to returning multiple values. Specifically, there's no reason we shouldn't be able to have copy/move elision just because you want to have a function return multiple values.
//syntactic sugar for tuple<int,float,double> foo()
[int,float,double] foo();
//Extract the first value, and store the tail in a tuple<float,double>
[auto x; auto... yz] = foo();
//Extract the values of yz later.
[auto y; auto z] = yz;
//Construct a tuple directly using new syntax
//Different from lambda because the [] contains only a list of types, not variable names
auto xyz = [int,float,double]();
On 2015-05-26 14:10, Nicol Bolas wrote:
> If we're going to have special syntax for using multiple return values, I
> would much rather we have actual multiple return value syntax, not
> something built on `tuple`.
>
> The reason to want this to be a legitimate language feature is so that we
> can have all of the aspects of return values still apply to returning
> multiple values. Specifically, there's no reason we shouldn't be able to
> have copy/move elision just because you want to have a function return
> multiple values.
Is there a reason this is not possible with returning std::tuple? Is
there a reason we should not support unpacking of e.g. std::pair,
std::tuple as return values?
std::pair<int,int> foo();
[auto x; auto y] = foo();
//is like
auto p = foo();
std::tuple<int,int> t = std::move(p);
auto x = std::get<0>(t);
auto y = std::get<1>(t);
(In particular I would like std::pair
unpacking for the sake of API's that already return a std::pair, where
unpacking would make sense. Like std::map::iterator.)
Put differently, is there a reason this needs to be part of the initial
proposal rather than a follow-on proposal? (That said, I *would* be in
favor of a simplified mechanism for returning multiple values;
std::tuple is... aesthetically challenged :-).)
I think the matching return and capture is a very important point, since a
lambda and this return are both basically anonymous structs.
return [x = 5, y = 10] {}
If it were possible to extract the captured variables, we'd have half the job
done.
// return type is a parameter-struct
struct (int a, int b, double) foo() {
return { 1, 2, 3.0 };
}
struct Test : decltype(foo()) // inherit from a parameter-struct
{
};
int main() {
int a = foo().a;
int b = foo().b;
int c = foo().member2; // unnamed member got a member + index name
int ai = foo().get<0>(); // auto generated getter
int bi = foo().get<1>();
double c = foo().get<2>();
foo().set<1>(2).get<1>(); // auto generated setter, returning *this
}
True :-). I'm not wedded to []'s (I could see ()'s too), it's more that
they don't *bother* me, the way they seem to bother some folks. Also
that it "feels" like they would be the easiest to parse without
additional typing or syntax ambiguities.
>>> I've chosen to use ; instead of , to avoid confusion with the comma
>>> operator.
>>
>> I strongly agree with using ';' to separate elements, since multiple
>> declarations should be permitted, but '[auto x, auto y]' clearly makes
>> no syntactic sense. That said, is ',' allowed inside the []'s at all? If
>> yes, what does it mean?
>>
>> [auto x, y] = bar(); // forces decltype(x) == decltype(y)?
>
> It should be equivalent to:
>
> auto t = bar();
> auto x, y = std::get<0>(t);
>
> Which is currently an error because x is not initialized.
Riiiight. That seems weird. I was thinking:
auto&& __temp = bar();
auto x = get<0>(__temp), y = get<1>(__temp);
...which anyway I'm not sure if that's legal (at least not for 'auto'),
but seems like a reasonable way to parse the above input. I'd lean
toward either that, or just make ',' (or at least 'a declaration
declaring more than one variable') an error in this context.
> You could have some horrible thing like this, which would be allowed:
>
> int& operator,(int& a, int& b) { return a; }
>
> int x;
> int y;
> [ x,y; void; void] = foo(); //assignes std::get<0>(foo()) to x
Well, yes, but that's just abusing an lvalue expression :-). We'd have
to explicitly forbid that, which doesn't seem worthwhile.
>> I'd be somewhat strongly inclined to follow Python, and not discard any
>> values. So the last item in the list becomes a tuple of whatever is left
>> over.
>
> This feature conflicts with allowing this syntax to work with any std::get
> compatible type. What type should auto... z be?
I'd say 'tuple'. It makes sense for 'tuple', and it makes sense for
weird things (e.g. some struct with std::get to get each member by
index) which otherwise would require the compiler to synthesize a new
type on the spot (which... sounds like a tuple anyway).
If we implemented it as, say, a new std::get<size_t from, size_t to>,
users would be able to provide partial specializations to override that
default behavior (and STL could do so for e.g. std::array). The
"default" would return a tuple of the specified values.
> tuple<int,int> a();
> array<int,2> b();
> pair<int,int> c();
>
> [auto xa; auto xva] = a(); //decltype(xva) is tuple<int>?
> [auto xb; auto xvb] = b(); //decltype(xvb) is array<int,1>?
> [auto xc; auto xvc] = c(); //What is decltype(xvc)???
Hmm... this seems like a good reason to require '...'. Then, only '...'
may consume multiple values (presuming we're going with the 'must
explicitly consume everything' rule), and a '...' *always* has a
multi-valued type, as above, even if there is only a single remaining value.
array<int,4> foo();
[auto x, auto... yzw] = foo();
//yzw is tuple<int,int,int> but I expected array<int,3>!
> Here is another generic code use case which is trickier if we are only
> based on std::get().
> Imagine we want to extract the second parameter, and pass long a
> concatenated list of the first and the tail.
>
> [auto first; auto second; auto... tail] = foo();
> use(first);
> auto params = concat({ first }, tail)
> bar(params)
>
> Such a thing is possible for tuple and array but not pair.
If 'auto...' can be empty, why would this not work? (In fact, why would
it work differently for tuple<T1, T2> than pair<T1, T2>?)
I'm not convinced that RVO can't happen with
this and e.g. std::tuple.
tuple<int,float,double> foo();
//RVO constructs t in place
auto t = foo();
auto x = std::move(get<0>(t));
//use x from here, but never touch t again
>> Put differently, is there a reason this needs to be part of the initial
>> proposal rather than a follow-on proposal? (That said, I *would* be in
>> favor of a simplified mechanism for returning multiple values;
>> std::tuple is... aesthetically challenged :-).)
>
> I agree std::tuple is ugly is somewhat non-obvious for beginners. Having a
> syntax for defining multiple return functions and also capturing the values
> would probably be the way to go.
Sure. But I agree with your comments; it seems to me also that this
should be syntactic sugar for std::tuple. This significantly lessens the
implementation burden (in particular, no new syntax for the return). And
that being the case, it may make sense to do it as a follow-on proposal.
(Hmm... should we allow '[int*3]' -> std::array<int, 3>? Again, though,
this feels like it should be a separate, follow-on proposal.)
[int,3] foo();
[auto x, auto yz] = foo();
//yz is array<int,2>
[auto y, auto z] yz;
[[int,2],float,[double,3]] x; //tuple<array<int,2>,float,array<double,3>> x;
[auto,*] x = {1, 2, 3}; //x is array<int,3>
I think we need a parameter struct like thing. A struct with a parameter list syntax, something like:
// return type is a parameter-struct
struct (int a, int b, double) foo() {
return { 1, 2, 3.0 };
}
struct Test : decltype(foo()) // inherit from a parameter-struct
{
};
int main() {
int a = foo().a;
int b = foo().b;
int c = foo().member2; // unnamed member got a member + index name
int ai = foo().get<0>(); // auto generated getter
int bi = foo().get<1>();
double c = foo().get<2>();
foo().set<1>(2).get<1>(); // auto generated setter, returning *this
}
Peter
inline struct (int a, int b, double) foo() {
return { 1, 2, 3.0 };
}
int main() {
foo();
std::cout << "Hello inline variables: " << a << b << member2 << std::end;
}
struct (int a, int b, double) foo() {
return { 1, 2, 3.0 };
}
int main() {
#!inlineVariables(foo());
std::cout << "Hello inline variables: " << a << b << member2 << std::end;
} I think we need a parameter struct like thing. A struct with a parameter list syntax, something like:
// return type is a parameter-struct
struct (int a, int b, double) foo() {
return { 1, 2, 3.0 };
}
struct Test : decltype(foo()) // inherit from a parameter-struct
{
};
int main() {
int a = foo().a;
int b = foo().b;
int c = foo().member2; // unnamed member got a member + index name
int ai = foo().get<0>(); // auto generated getter
int bi = foo().get<1>();
double c = foo().get<2>();
foo().set<1>(2).get<1>(); // auto generated setter, returning *this
}
int bi = foo().size(); // returns 3
static_assert(foo().size() == 3);
The question here is really whether you want
1. a syntax and language support for multiple return values
or
2. a destructuring syntax for tuple/pair (or anything compatible with
unqualified get<N>(x))
These are orthogonal features.
On Tuesday, May 26, 2015 at 3:07:27 PM UTC-4, Matthew Woehlke wrote:I'm not convinced that RVO can't happen with
this and e.g. std::tuple.
I'm not convinced either.
For example, this should be possible today:
tuple<int,float,double> foo();
//RVO constructs t in place
auto t = foo();
auto x = std::move(get<0>(t));
//use x from here, but never touch t again
There is no reason the compiler can't completely elide the move/copy construction of x and just keep referencing std::get<0>(t) which itself was RVO constructed.
auto x = std::move(<<<insert any expression here>>>);
auto x = FunctionCall(...);
On 2015-05-26 14:29, Matthew Fioravante wrote:
> On Tuesday, May 26, 2015 at 2:24:22 PM UTC-4, Matthew Woehlke wrote:
>> Is there a reason this is not possible with returning std::tuple? Is
>> there a reason we should not support unpacking of e.g. std::pair,
>> std::tuple as return values?
>
> std::pair<X,Y> is convertible to std::tuple<X,Y> [...]
I think you missed the point :-). Nicol seemed to be implying a
(performance?) benefit to only supporting some new, special built-in
tuple-like type, as opposed to std::tuple (or more generally, classes
that support std::get). I'm not convinced that RVO can't happen with
this and e.g. std::tuple.
On 2015-05-26 16:00, Matthew Fioravante wrote:
> Is there any real reason to actually support anything other than tuple
> (with implicit conversion from pair) and array?
QPair. QSize. Other weird user-defined classes that "look like"
std::tuple, std::pair, or std::array. Existing API's that currently
return a struct because they predate std::tuple or considered it "too
ugly". (I have a range struct, i.e. a "pair" with lower/upper, that
might benefit.)
For that matter, tuple is *still* ugly in failing to name its elements.
I can readily imagine that even with unpacking, some folks will want to
return structs, but would like to be able to unpack them.
In general, yes, I think adds value if unpacking can be extended by the
user to work on new types, i.e. by providing std::get for such types.
> Converting a pair to a tuple is natural and probably won't result in too
> many surprises, converting array to tuple not so much.
This, and the above, is why I keep saying that the tail should use
something *like-but-not-exactly* std::get<size_t>, so that STL and the
user can specialize it if it makes sense for the tail to be something
other than std::tuple. (Note that in saying that, I am implying that STL
*would* specialize so that a tail of std::array is a std::array.)
We should think more specifically about what such an API would look like and what it takes for a user to implement one for a simple case such as struct { float x; float y; float z; }. Not only do we need std::get<int> but also std::get<int,int> for the mutli-value capture and all of these need to correctly fail compilation for an invalid index while simultaneously supporting recursive iteration (SFINAE).
You can be convinced of whatever you want; the standard doesn't agree. Elision can only take place for the initialization of parameters to functions and the initialization of return values directly from functions. Therefore, if a function returns a tuple, then the tuple you store it in can elide the copy/move. However, if you then want to extract a member of this tuple, whether via `std::tie` or via `std::get<>` or whathaveyou, that's constructing a new value.
[const auto& x; auto& y] = foo();
//expands to:
auto t = foo();
const auto& x = std::get<0>(t);
auto& y = std::get<1>(t);
auto t = [int,float]();
[auto& i, auto& f] = t;
Also, I should make this clear: I'm not supporting the idea of returning a "tuple-like type". I'm saying that we should have multiple return values from functions. Just like functions can have multiple parameters.
You don't stick your parameters in a `tuple` just to call a function, so why should you pull them from a `tuple` when you return them? It's purely a language limitation, and one that is decidedly artificial.
[int, long, double] foo();
[int, long, double] x;
void bar(int, long, double);
void car(tuple<int, long, double>);
bar([*]foo());
bar([*]x);
car(foo());
car(x);
On Tuesday, May 26, 2015 at 4:42:29 PM UTC-4, Nicol Bolas wrote:Also, I should make this clear: I'm not supporting the idea of returning a "tuple-like type". I'm saying that we should have multiple return values from functions. Just like functions can have multiple parameters.
You don't stick your parameters in a `tuple` just to call a function, so why should you pull them from a `tuple` when you return them? It's purely a language limitation, and one that is decidedly artificial.
Why not?
Conceptually, there is little difference between a type list and list of function parameters. You could think of calling a function as "take these local variables, arrange them into a type list, and call the function with that list of parameters". Some languages like python let you expand a tuple in place to call a function with it as the parameters. Python also handles multiple return value capture using tuple syntax.
As was stated earlier, doing it this way would be a huge change that likely would never make it past the committee.
Each platform will have to define a new calling convention for multiple return valued functions. We would never get support for interfaces which already return pair and tuple and they could never be changed because it would break old code. Other than potential performance concerns with RVO that might be alleviated by using references, what is the real benefit of doing it as a pure language feature like this?
How would you for example support a range for loop with std::unordered_map? The iterator dereference already returns a std::pair.
On 2015–05–27, at 8:14 AM, Nicol Bolas <jmck...@gmail.com> wrote:... std::tie takes a tuple and exports the members of that tuple into variables. The opposite of that would be to take variables and combine them into a tuple. That's called `make_tuple`; you don't need special syntax for that.
Also, I have no idea what that has to do with multiple return values.
On Tuesday, May 26, 2015 at 5:23:20 PM UTC-4, Matthew Fioravante wrote:
On Tuesday, May 26, 2015 at 4:42:29 PM UTC-4, Nicol Bolas wrote:Also, I should make this clear: I'm not supporting the idea of returning a "tuple-like type". I'm saying that we should have multiple return values from functions. Just like functions can have multiple parameters.
You don't stick your parameters in a `tuple` just to call a function, so why should you pull them from a `tuple` when you return them? It's purely a language limitation, and one that is decidedly artificial.
Why not?
Because it requires needless copy/moving.
void foo(int x, const string& s);
tuple<int,string> t;
foo([*]t); //expands to foo(get<0>(t), get<1>(t));
Because it looks like worse syntax than function parameters, even with your terse version. Because it provides absolutely zero descriptive information about what those parameters mean.
[int,float,double] foo() { return {1, 1.0f, 1.0 }; }
Because it's entirely pointless, and has been since we got variadic templates. Unless you happen to have a collection of values that are already in a tuple, there's no reason to pass a tuple to a function as a set of parameters.
Conceptually, there is little difference between a type list and list of function parameters. You could think of calling a function as "take these local variables, arrange them into a type list, and call the function with that list of parameters". Some languages like python let you expand a tuple in place to call a function with it as the parameters. Python also handles multiple return value capture using tuple syntax.
Yeah, and Python also handles multiple return values as a first-class language feature. It doesn't force you to package your multiple return values into a tuple, return them, and then unpack them.
Nor does it force you to package parameters into a tuple.
def foo():
return (1, 2, 3);
(x, y, z) = foo();
So you're kind of proving my point for me.
It should also be noted that Python:
1) Is not a statically typed language.
2) Is not a strongly typed language.
Because of these, Python doesn't have "tuples" at all. It simply has arrays (and other data structures), which can contain values by index. It has a function that can take an array and return that array as though it were a sequence of values. And so forth. Because Python is dynamically typed, the distinction between "tuple" and "array" simply doesn't exist.
So Python doesn't have the issues that C++ does.As was stated earlier, doing it this way would be a huge change that likely would never make it past the committee.
But it's the right way to do it. And quite frankly, we don't need multiple return values so badly that we should do it the wrong way just to have some convenient way to do it.
Some people use tuples a lot (some might say to the point of overuse). But you shouldn't steal potentially useful syntax from those C++ programmers who don't use them.
Syntax is a precious commodity, which once apportioned, cannot be taken back.
It should also be noted that reflection, with the full ability to compile-time inspect and generate types with full syntax (including variable names) makes tuples... a dubious proposition for the future of C++. With reflection, you can index a struct by number. With reflection, you can take any typelist and build an honest-to-God struct out of it (which can be standard layout and such).
In short, it is entirely possible that tuples will become obsolete, thus also obsoleting this kind of syntax. So let's not have another std::bind happen here...
[int,long,double] foo();
auto x = foo();
Each platform will have to define a new calling convention for multiple return valued functions. We would never get support for interfaces which already return pair and tuple and they could never be changed because it would break old code. Other than potential performance concerns with RVO that might be alleviated by using references, what is the real benefit of doing it as a pure language feature like this?
Well sure, if you ignore all of the advantages of something, then yes there's not much point to it.How would you for example support a range for loop with std::unordered_map? The iterator dereference already returns a std::pair.
To extend it for multiple return values, all you need is an alternate iterator type that returns multiple values.
for([auto& k; auto& v] : make_multi_return_range(hashmap)){}
for(auto& kv: hashmap);
for([auto& k; auto& v]: hashmap);
On Tuesday, May 26, 2015 at 9:23:50 PM UTC-4, Nicol Bolas wrote:On Tuesday, May 26, 2015 at 5:23:20 PM UTC-4, Matthew Fioravante wrote:On Tuesday, May 26, 2015 at 4:42:29 PM UTC-4, Nicol Bolas wrote:Also, I should make this clear: I'm not supporting the idea of returning a "tuple-like type". I'm saying that we should have multiple return values from functions. Just like functions can have multiple parameters.
You don't stick your parameters in a `tuple` just to call a function, so why should you pull them from a `tuple` when you return them? It's purely a language limitation, and one that is decidedly artificial.
Why not?
Because it requires needless copy/moving.I don't see any needless copying happening below.
void foo(int x, const string& s);
tuple<int,string> t;
foo([*]t); //expands to foo(get<0>(t), get<1>(t));
Because it looks like worse syntax than function parameters, even with your terse version. Because it provides absolutely zero descriptive information about what those parameters mean.You yourself suggested the following:
[int,float,double] foo() { return {1, 1.0f, 1.0 }; }All that tells me is that foo() returns an int, float, and double. How is this more descriptive than a tuple?
Because it's entirely pointless, and has been since we got variadic templates. Unless you happen to have a collection of values that are already in a tuple, there's no reason to pass a tuple to a function as a set of parameters.Magnus just showed a use case for this.. Composing multi-return functions together.
Even if tuple is not used, something just like this would be need to say "unpack the multi-return values and forward them directly to the next function".
Nor does it force you to package parameters into a tuple.But it does, at least at the syntax level
def foo():
return (1, 2, 3);
(x, y, z) = foo();
function foo()
return 1, 2, 3 --Returns a sequence of 3 values.
end
function bar()
return {1, 2, 3} --Returns a single table, containing 3 values
end
local x, y, z = foo() --Declares 3 local variables, initializing them with 3 values from the function.
local w = {foo()} --Stores the sequence of values returned from `foo` in a table (aka: array)
local a = bar() --Stores the table
local comp = bar(foo()) --The sequence is passed to `bar` as 3 parameters.
local comp2 = bar(bar()) --A single parameter is passed to the outer `bar`.
local comp3 = bar(unpack(bar())) --Unpacks the array into the outer `bar`'s parameters
As was stated earlier, doing it this way would be a huge change that likely would never make it past the committee.
But it's the right way to do it. And quite frankly, we don't need multiple return values so badly that we should do it the wrong way just to have some convenient way to do it.
Some people use tuples a lot (some might say to the point of overuse). But you shouldn't steal potentially useful syntax from those C++ programmers who don't use them.Programmers who have no interest in tuple itself can just use the syntax to return multiple values and unpack them at the call site. They don't have to care that tuple (or something tuple like) is used to accomplish this.
auto single_value()
{
std::string a = ...;
return a;
}
auto multi_value()
{
std::string a = ...;
std::vector b = ...;
return [a; b];
}
Syntax is a precious commodity, which once apportioned, cannot be taken back.
It should also be noted that reflection, with the full ability to compile-time inspect and generate types with full syntax (including variable names) makes tuples... a dubious proposition for the future of C++. With reflection, you can index a struct by number. With reflection, you can take any typelist and build an honest-to-God struct out of it (which can be standard layout and such).
In short, it is entirely possible that tuples will become obsolete, thus also obsoleting this kind of syntax. So let's not have another std::bind happen here...
[int,long,double] foo();
auto x = foo();Ok, if tuple is out then what is decltype(x) in the above example doing things your way?
Another kind of typelist which is standard layout compatible? Or are we no longer able to capture all of the return values into one object?
auto x = make_tuple(foo());
auto x = make_array(foo());
auto x = std::vector<Type>{foo()};
If not using tuple, can you explain in a bit more detail how your proposed solution would actually work?
On 27 May 2015, at 09:43 , Nicol Bolas <jmck...@gmail.com> wrote:[int,long,double] foo();
auto x = foo();Ok, if tuple is out then what is decltype(x) in the above example doing things your way?
There are two defensible answers to this:
1) A compilation error. You called a function that returns 3 things, but you only captured one. That suggests you weren't paying attention.
2) `int`. The other two types are dropped on the floor.
#2 is probably the way to go. Any sort of aggregate object type is not even up for discussion.
Multiple return values means multiple return values, not "pack multiple values into some kind of object and unpack them later".
Another kind of typelist which is standard layout compatible? Or are we no longer able to capture all of the return values into one object?
If you want to capture them all in an object, you just ask for it:auto x = make_tuple(foo());
That is neither hard nor particularly verbose. You can even stick them in an array, if all of the returned values are of the same type:auto x = make_array(foo());
Or in an arbitrary data structure:auto x = std::vector<Type>{foo()};
But most people using multiple return values don't capture them in a big object, so we don't make the syntax optimal for that case.
On 27 May 2015, at 09:43 , Nicol Bolas <jmck...@gmail.com> wrote:[int,long,double] foo();
auto x = foo();Ok, if tuple is out then what is decltype(x) in the above example doing things your way?
There are two defensible answers to this:
1) A compilation error. You called a function that returns 3 things, but you only captured one. That suggests you weren't paying attention.
2) `int`. The other two types are dropped on the floor.
#2 is probably the way to go. Any sort of aggregate object type is not even up for discussion.
Multiple return values means multiple return values, not "pack multiple values into some kind of object and unpack them later".
Another kind of typelist which is standard layout compatible? Or are we no longer able to capture all of the return values into one object?
If you want to capture them all in an object, you just ask for it:auto x = make_tuple(foo());
That is neither hard nor particularly verbose. You can even stick them in an array, if all of the returned values are of the same type:auto x = make_array(foo());
Or in an arbitrary data structure:auto x = std::vector<Type>{foo()};
But most people using multiple return values don't capture them in a big object, so we don't make the syntax optimal for that case.I assume Matthew’s point comes from dealing with template code.template<class F>??? contrived_example(F f) {return f();}The author would probably expect that ??? can be plain “auto”, implying “in some places auto can expand to multiple types”.
Then there’s the point of what generic code would require to do to consistently capture the return value(s) of a Callable without knowing if there are one or multiple return values.template<class F>void contrived_example2(F f) {??? x = f();...}
In Lua, you don't need special syntax for returning or capturing multiple values. And quite frankly, I see no reason why you should:
function foo()
return 1, 2, 3 --Returns a sequence of 3 values.
end
function bar()
return {1, 2, 3} --Returns a single table, containing 3 values
end
local x, y, z = foo() --Declares 3 local variables, initializing them with 3 values from the function.
local w = {foo()} --Stores the sequence of values returned from `foo` in a table (aka: array)
local a = bar() --Stores the table
local comp = bar(foo()) --The sequence is passed to `bar` as 3 parameters.
local comp2 = bar(bar()) --A single parameter is passed to the outer `bar`.
local comp3 = bar(unpack(bar())) --Unpacks the array into the outer `bar`'s parameters
I find that much more reasonable and consistent than the Python syntax. There is a distinction between a sequence of values and a data structure. `foo` and `bar` may be "conceptually" the same, but functionally they're different. One returns a sequence of values, the other returns a table. If you need to convert a table into a sequence of values, you call `unpack`. If you need to convert a sequence of values into a table, you create a table with it.
Sequences of values in Lua are ephemeral; if you want to store the sequence and preserve it as a sequence, you stick it in a table. But if you just want to compose functions, store values, and the like, you use the sequence as is.
If C++ is going to pattern its functionality off of something, I'd much rather Lua's proper MRV syntax than Python's MRVs-as-arrays (I assume the () syntax is creating an array of some kind).
[int,int,int] foo();
int bar();
//Initializes x, y, z using foo's return values??
int x, y, z = foo();
//Initializes only c using bar's() return value and leaves a and b uninitialized
int a, b, c = bar();
[int,long,double] foo();
auto x, y, z = foo(); //x is int, y is long, z is double
As was stated earlier, doing it this way would be a huge change that likely would never make it past the committee.
But it's the right way to do it. And quite frankly, we don't need multiple return values so badly that we should do it the wrong way just to have some convenient way to do it.
Some people use tuples a lot (some might say to the point of overuse). But you shouldn't steal potentially useful syntax from those C++ programmers who don't use them.Programmers who have no interest in tuple itself can just use the syntax to return multiple values and unpack them at the call site. They don't have to care that tuple (or something tuple like) is used to accomplish this.
Except that they have to pay the cost for it. Consider something simple like this:
auto single_value()
{
std::string a = ...;
return a;
}
auto multi_value()
{
std::string a = ...;
std::vector b = ...;
return [a; b];
}
`single_value` is subject to copy elision, so you should feel no problem with using it. Even with a small-string-optimized std::string class, you will get the maximum possible performance if you're using the result to initialize an object.
With language-level multiple return values, `multi_value` would be able to operate under the same principle. `a` and `b` would be considered return values, and they would each be separately subject to elision.
With your way, that's just not possible. If that syntax creates a `tuple`, then it must copy or move initialize the elements of that `tuple`. So even though the `tuple` itself is subject to elision, the creation of it is not.
Syntax is a precious commodity, which once apportioned, cannot be taken back.
It should also be noted that reflection, with the full ability to compile-time inspect and generate types with full syntax (including variable names) makes tuples... a dubious proposition for the future of C++. With reflection, you can index a struct by number. With reflection, you can take any typelist and build an honest-to-God struct out of it (which can be standard layout and such).
In short, it is entirely possible that tuples will become obsolete, thus also obsoleting this kind of syntax. So let's not have another std::bind happen here...
[int,long,double] foo();
auto x = foo();Ok, if tuple is out then what is decltype(x) in the above example doing things your way?
There are two defensible answers to this:
1) A compilation error. You called a function that returns 3 things, but you only captured one. That suggests you weren't paying attention.
2) `int`. The other two types are dropped on the floor.
#2 is probably the way to go. Any sort of aggregate object type is not even up for discussion.
[int,float] foo();
[auto x; auto y] = foo(); //Ok captures into x and y
[auto x; void] = foo(); //Ok captures first into x, throws away second
auto x = foo(); //Captures only first??
void foo(int, float);
[int, float] bar();
auto... x = bar();
foo(x...);
foo(x...);
Multiple return values means multiple return values, not "pack multiple values into some kind of object and unpack them later".
Another kind of typelist which is standard layout compatible? Or are we no longer able to capture all of the return values into one object?
If you want to capture them all in an object, you just ask for it:
auto x = make_tuple(foo());
That is neither hard nor particularly verbose. You can even stick them in an array, if all of the returned values are of the same type:
auto x = make_array(foo());
Or in an arbitrary data structure:
auto x = std::vector<Type>{foo()};
But most people using multiple return values don't capture them in a big object, so we don't make the syntax optimal for that case.
If not using tuple, can you explain in a bit more detail how your proposed solution would actually work?
The details of how it works are more or less the way Lua works.
In C++ parlance, the notion of a "sequence of values" would be a construct. It would not necessarily be an "expression", in the same way that a braced-init-list is not an expression. Since it's a new construct, you can give it whatever properties you want.
Functions can return sequences of values, with the sequence as a full part of its return type. Using the sequence initialization syntax, you can initialize/assign multiple variables to a sequence of values. If a sequence of values is given to a function argument list, the values are placed in that list as arguments. If the sequence of values is used in any other kind of expression, then it resolves down to the first element of that sequence of values.
Initialization/assignment of variables through a sequence would work element by element, in order from left to right.
struct kv {
string key;
int value;
};
kv kv;
[auto k; auto v] = kv;
template <typename K, typename V>
class HashMap {
public:
struct kv { K key; V value; };
class iterator : public std::iterator<std::bidirectional_iterator_tag,kv> { /* ... */ };
//...
};
HashMap hashmap = /* something */;
//Iterate using kv struct (more descriptive than pair)
for(auto& kv: hashmap) {
doSomething(kv.key, kv.value);
}
//Iterate by unpacking the kv struct first
for([auto& key; auto& value]: hashmap) {
doSomething(key, value);
}
//Iterate over only the keys
for([auto& key; void]: hashmap) {}
//Iterate over only the values
for([void; auto& value]: hashmap) {}
I assume Matthew’s point comes from dealing with template code.template<class F>??? contrived_example(F f) {return f();}The author would probably expect that ??? can be plain “auto”, implying “in some places auto can expand to multiple types”. Then there’s the point of what generic code would require to do to consistently capture the return value(s) of a Callable without knowing if there are one or multiple return values.template<class F>void contrived_example2(F f) {??? x = f();...}Should ??? be something that can potentially capture a single or multiple values depending on the initialization? If so what can be done with x down the pipeline? Or is a concept necessary to overload contrived_example2 for functions with single or multiple return values? Is template code forced into packing every Callable’s invocation into make_tuple() just in case it might return multiple values?
Instead, why not allow MRV to invoke a conversion function? It's minimal boilerplate for the library author:struct kv { K key; V value; operator[K, V] { return [key, value]; } };MRV conversion functions could be added to pair and tuple in the library, meaning there's no need to invoke reflection or privilege std::get<N>.
On Wednesday 27 May 2015 11:21:47 Matthew Woehlke wrote:
> In Python, "return x, y" is syntactic sugar for "return (x, y)". I
> suppose we could allow similar sugar in C++, possibly with the
> requirement that the function was declared with the tuple sugar (as in
> the declarations in the above example) in order to avoid changing the
> meaning of existing code w.r.t. 'operator,'.
Please don't, because operator, can be overloaded. Using it is one of the
tricks to handling void returns.
[int,float] foo() { return { 1, 2.0f }; }
[int,float] bar() { return [int,float](1, 2.0f); }
On Wednesday, May 27, 2015 at 3:43:10 AM UTC-4, Nicol Bolas wrote:Except that they have to pay the cost for it. Consider something simple like this:
auto single_value()
{
std::string a = ...;
return a;
}
auto multi_value()
{
std::string a = ...;
std::vector b = ...;
return [a; b];
}
`single_value` is subject to copy elision, so you should feel no problem with using it. Even with a small-string-optimized std::string class, you will get the maximum possible performance if you're using the result to initialize an object.
With language-level multiple return values, `multi_value` would be able to operate under the same principle. `a` and `b` would be considered return values, and they would each be separately subject to elision.
With your way, that's just not possible. If that syntax creates a `tuple`, then it must copy or move initialize the elements of that `tuple`. So even though the `tuple` itself is subject to elision, the creation of it is not.
So whats stopping someone from writing a proposal to allow copy/move elision in your multi_value() example?
Just because it can't happen now doesn't mean we can't change the standard. Multi return values would be a primary use case for such a proposal.
Also what if I want to save the sequence so that I can forward it multiple times?
Using your approach, I might suggest allowing auto... to capture all of the return values so that they can be forwarded along. This could work just like a variadic template parameter pack.
void foo(int, float);
[int, float] bar();
auto... x = bar();
foo(x...);
foo(x...);
But we still have the generic code problems that Miro brought up.
Multiple return values means multiple return values, not "pack multiple values into some kind of object and unpack them later".
Another kind of typelist which is standard layout compatible? Or are we no longer able to capture all of the return values into one object?
If you want to capture them all in an object, you just ask for it:
auto x = make_tuple(foo());
That is neither hard nor particularly verbose. You can even stick them in an array, if all of the returned values are of the same type:
auto x = make_array(foo());
These require copy/move. In your example, unpacking is free (no-op) and packing requires a copy/move. In my example unpacking is free (using references) and packing is also free, assuming you want to continue using the pack type returned by the function.
Or in an arbitrary data structure:
auto x = std::vector<Type>{foo()};
But most people using multiple return values don't capture them in a big object, so we don't make the syntax optimal for that case.
Its not uncommon. Every class that has multiple "values" (e.g unordered_map) will be returning them in a pack using its default iterators so that one can write a simple range for loop.
On Wednesday, May 27, 2015 at 11:11:34 AM UTC-4, Matthew Fioravante wrote:On Wednesday, May 27, 2015 at 3:43:10 AM UTC-4, Nicol Bolas wrote:Except that they have to pay the cost for it. Consider something simple like this:
auto single_value()
{
std::string a = ...;
return a;
}
auto multi_value()
{
std::string a = ...;
std::vector b = ...;
return [a; b];
}
`single_value` is subject to copy elision, so you should feel no problem with using it. Even with a small-string-optimized std::string class, you will get the maximum possible performance if you're using the result to initialize an object.
With language-level multiple return values, `multi_value` would be able to operate under the same principle. `a` and `b` would be considered return values, and they would each be separately subject to elision.
With your way, that's just not possible. If that syntax creates a `tuple`, then it must copy or move initialize the elements of that `tuple`. So even though the `tuple` itself is subject to elision, the creation of it is not.
So whats stopping someone from writing a proposal to allow copy/move elision in your multi_value() example?
The fact that C++ can't do that. Remember: your [values...] syntax equates to the construction of a `std::tuple` from the given values. Well, the memory for those values has already been allocated, and if those are variables, then the memory there must have been allocated elsewhere. And because `std::tuple` is not special, because it must follow the regular rules of C++, there's no way for a compiler to know (without very non-local optimizations) exactly what that constructor is doing.
Furthermore, even if a compiler did know... what of it? Are you suggesting that the compiler can, or should be allowed to, initialize the memory space in a `tuple` before the object is even constructed, then ignore the tuple's constructor?
template<class... T> [T...] tuple<T...>::operator*() { return [get<0...sizeof...(T...)>(*this)...]; }
auto x = make_tuple(f());
g(*x);
On Wednesday, May 27, 2015 at 11:59:36 AM UTC-4, Nicol Bolas wrote:On Wednesday, May 27, 2015 at 11:11:34 AM UTC-4, Matthew Fioravante wrote:On Wednesday, May 27, 2015 at 3:43:10 AM UTC-4, Nicol Bolas wrote:Except that they have to pay the cost for it. Consider something simple like this:
auto single_value()
{
std::string a = ...;
return a;
}
auto multi_value()
{
std::string a = ...;
std::vector b = ...;
return [a; b];
}
`single_value` is subject to copy elision, so you should feel no problem with using it. Even with a small-string-optimized std::string class, you will get the maximum possible performance if you're using the result to initialize an object.
With language-level multiple return values, `multi_value` would be able to operate under the same principle. `a` and `b` would be considered return values, and they would each be separately subject to elision.
With your way, that's just not possible. If that syntax creates a `tuple`, then it must copy or move initialize the elements of that `tuple`. So even though the `tuple` itself is subject to elision, the creation of it is not.
So whats stopping someone from writing a proposal to allow copy/move elision in your multi_value() example?
The fact that C++ can't do that. Remember: your [values...] syntax equates to the construction of a `std::tuple` from the given values. Well, the memory for those values has already been allocated, and if those are variables, then the memory there must have been allocated elsewhere. And because `std::tuple` is not special, because it must follow the regular rules of C++, there's no way for a compiler to know (without very non-local optimizations) exactly what that constructor is doing.
If tuple (or whatever type we use, lets call it tuple) constructor is inline and just trivially constructs the members, as it should be then the compiler can do whatever it wants. I don't see why the constructor cannot construct the string directly in the memory to be used by the returned tuple.
struct Simple
{
std::string str;
int foo;
};
std::string in = ...;
return Simple{in, 2};
Furthermore, even if a compiler did know... what of it? Are you suggesting that the compiler can, or should be allowed to, initialize the memory space in a `tuple` before the object is even constructed, then ignore the tuple's constructor?
Sure, if the constructor is inline and we can prove "as-if" compatible behavior why not?
template<typename ...T> class initializer_list {}
template<typename ...T> class var_initializer_list {}
template<typename ...Types>
explicit tuple (const Types&... elems);
template<typename ...Types>
tuple (const initializer_list<Types...>& elems);
On 2015-05-27 16:06, Nicol Bolas wrote:
> struct Simple
> {
> std::string str;
> int foo;
> };
>
> std::string in = ...;
>
> return Simple{in, 2};
>
> [...] So... how would "partial constructor elision" work? Would it
> only be restricted to aggregates?
At least restricted to only aggregates, yes. Anything else, as you note,
is... "fun".
You are *also* proposing something almost, if not exactly, equivalent
for your "value sequence" concept. If we define "value sequence" as some
tuple-like-but-not-tuple "object" for which this sort of in-place
construction is allowed, then I don't see why this needs to be tightly
coupled to unpacking.
> The questions you need to answer are [...] Can you actually implement
> it?
Can you actually implement your MRV concept? Seems like it has most of
the same issues...
Aside from determining when to employ elision, at least, but then, if
the return type supports unpacking (which it obviously should), I don't
see why we need to get so worked up about it.
That said, this notion of a single "entity" that is really multiple
values... bugs me. It would be one thing if we had syntax that said
'hey, this here is multiple values that I would like passed as separate
parameters', but my understanding is that that *isn't* what Nicol is
suggesting. Given that we don't have any similar concept in the language
currently, I'm not particularly fond of introducing it.
So this idea came out of the number parsing thread. Many languages like python have support for functions returning multiple values.--In C++, one option we have currently is this:
std::tuple<int,float,double> foo();
int x;
double d;
std::tie(x, std::ignore, d) = foo();
This works but it has a lot of problems:- We have to declare the receiving variables beforehand. This means they have to be default constructed and then assigned and we cannot use auto.- Its overly verbose and clumsy to use. First we have to declare the variables, then we need 8 character std::tie and 11 character std::ignore.- tuple and tie are not as well known to beginners. A "standard practice" for capturing multiple values would be better as a core language feature than a library feature.So one solution would be to invent a new syntax:
int x;
[x; void; auto d] = foo();Which is the equivalent of:
int x;
auto _t = foo();x = std::get<0>(std::move(_t));
auto d = std::get<2>(std::move(_t));Each element in this "assignment list" corresponds to a element of a tuple of the right hand side of the expression. Each element can be one of:- An lvalue expression: the resulting lvalue is assigned with operator=() to the corresponding tuple element.- void: the corresponding tuple element is ignored- A local variable declaration. A local variable is created and constructed with the corresponding tuple element.I've chosen to use [] instead of {} because so far braces always introduce a scope and in this example, d which is created inside the braces actually lives in the outer scope.I've chosen to use ; instead of , to avoid confusion with the comma operator.We can also allow the user to specify a shorter list, with default values of void for the trailing elements not mentioned:
[auto x] = foo(); //same as [auto x, void, void] = foo()
What do you think about such a feature?
auto & bar = foo();
int x = std::get<0>(bar);
auto d = std::get<2>(bar);
On 28 May 2015, at 00:55 , Thiago Macieira <thi...@macieira.org> wrote:On Wednesday 27 May 2015 15:09:33 Nicol Bolas wrote:We do have a similar concept: template parameter packs. It is one entity
which represents multiple objects. You can unpack them in arbitrary
expressions, which makes them much more useful than your limited `unpack`
tuple suggestion.
Actually, thinking about it and fold expressions allows for very easy
unpacking. The unpack-tuple-and-call function would be:
template <typename F, typename... Args> auto
unpack(F &&f, std::tuple<Args...> &&args)
{
using namespace std;
return f(std::forward<Args>(get<N>(args)), ...);
}
On 2015-05-27 03:43, Nicol Bolas wrote:
> On Tuesday, May 26, 2015 at 10:53:05 PM UTC-4, Matthew Fioravante wrote:
>> On Tuesday, May 26, 2015 at 9:23:50 PM UTC-4, Nicol Bolas wrote:
>>> Nor does it force you to package parameters into a tuple.
>>
>> But it does, at least at the syntax level
>>
>> def foo():
>> return (1, 2, 3);
>>
>> (x, y, z) = foo();
Actually it doesn't:
>>> def foo():
>>> return 1, 2
>>> a, b = foo()
...but the return is a 'tuple' nevertheless:
>>> type(foo())
<type 'tuple'>
(Basically, the ()'s are optional, and implied if absent.)
My knowledge of Lua is limited ("effectively nonexistent" is probably
more accurate). Python, however, only supports single return values. It
just has syntax sugar for returning tuples and unpacking tuples.
[...]
In Python, "return x, y" is syntactic sugar for "return (x, y)".
On 2015-05-27 18:05, Nicol Bolas wrote:
> On Wednesday, May 27, 2015 at 4:42:24 PM UTC-4, Matthew Woehlke wrote:
>> If we define "value sequence" as some tuple-like-but-not-tuple
>> "object" for which this sort of in-place construction is allowed,
>> then I don't see why this needs to be tightly coupled to
>> unpacking.
>
> This is the thing you don't seem to fully understand.
>
> I do not *want* a "value sequence" to be *any* kind of C++ object. It
> should not be a type with a value.
I think I do understand. It's just that I strongly disagree.
At some point, there *has* to be an object representing a value
sequence. Even if it's not a "C++ object", the compiler needs some sort
of internal notion that it has so many values and they are stored in
such-and-such manner.
If it has this, I fail to see why there is a problem giving that
construct a C++ name such that one can write get<N> against the object.
> Remember: template parameter packs are not objects either. You
> can't copy them, move them, reference them, or use them in anything other
> than a parameter pack expression.
Template parameter packs *also* are not real C++ entities. They are
template meta-programming entities that are much more closely related to
template type names. Substitution occurs at compile time, at instantiation.
This is not the case with return values, multiple or otherwise. For one,
the types are not templated (or it they are, that is an orthogonal process).
> Similarly, braced-init-lists are not
> elements of an expression; the construct `{4.3, "foo"}` is not a legal
> expression.
You're confusing the issue. That's *not* valid without context, no, but
we're not talking about a context-free case. And '{1, 2}' *is* valid; on
its own, it is a std::initializer_list<int>.
int a[4] = {1, 2, 3, 4};
int b[4];
b = {1, 2, 3, 4};
> Value sequences should be like those.
They won't be. A brace expression such as the above is *always* a brace
expression.
>> Can you actually implement your MRV concept? Seems like it has most of
>> the same issues...
>
> That's a fair question, but I fail to see the implementation problems.
> Certainly, they're nothing like those for defining and implementing elision
> out of some type.
That is *exactly* a problem. In your proposal, you expect this:
std::vector<int> a = {1, 2, 3};
std::string b = "foo";
return [a; b];
...to deduce that the 'a' and 'b' objects should NOT be constructed on
the stack, but rather in some manner that allows RVO.
I fail to see why it should be any more difficult to do this if the MRV
is some new "object type".
> Right now, as I understand it, output values at the compiled code level are
> returned as either registers (for small objects), or the caller provides a
> piece of stack space to store the returned object (for larger objects).
So what's the problem giving std::value_sequence this behavior?
It seems to me that std::value_sequence can be treated as a struct with
members equal to the set of return values *that are not constructed by
the caller*. The caller provides this storage as it would for e.g. a
std::tuple. The callee constructs the "members" of this in place in the
usual manner of RVO. No ABI change is needed.
>> I don't see why we need to get so worked up about it.
>
> Because syntax is rare and precious, it is not cheap. And valuable syntax
> should not be created just because it makes the lives of generic
> programmers who work with tuples easier.
I *don't* work with tuples. The *reason* I don't work with tuples is
because they are a pain, exactly *because* we don't have syntax to make
them easier.
Nor is unpacking limited to tuples. Aside from the range-based for
example (which, btw, I consider as a "critical" use case for any form of
unpacking and/or MRV),
auto x = Typename{FuncThatReturnsMultipleValues()};
unpacking has potential value for any
sequence-like structure. Think about arrays, points, ranges,
dimensions... all could potentially use unpacking.
Python obviously felt that *generalized* unpacking is sufficiently
valuable that it is not only a language feature, but one that uses
almost *no* syntax.
(2) Passing the "multiple return values" of one function as the "multiple arguments" to another. This is solved already in C++17: http://en.cppreference.com/w/cpp/utility/functional/invoke, inherited from Boost Fusion http://www.boost.org/doc/libs/1_58_0/libs/fusion/doc/html/fusion/functional/invocation/functions/invoke.html.
(Sigh... I had a reply and TB ate it. Going to try to cover the high
points; sorry if this ends up being a bit terse.)
On 2015-05-28 15:11, Nicol Bolas wrote:
> On Thursday, May 28, 2015 at 12:14:01 PM UTC-4, Matthew Woehlke wrote:
>> On 2015-05-27 18:05, Nicol Bolas wrote:
>>> Similarly, braced-init-lists are not
>>> elements of an expression; the construct `{4.3, "foo"}` is not a legal
>>> expression. Value sequences should be like those.
>>
>> They won't be. A brace expression such as the above is *always* a brace
>> expression.
>
> There is no such thing as a "brace expression". The standard is *very clear*
> about this: it is not an expression.
Please don't language lawyer :-).
What I mean is, the symbols '{a, b}' show me that there are two items.
In no way can I, at present, "pass" those two items in a way that I lose
the immediately obvious fact that there are two items.
What I object to is:
> auto x = Typename{FuncThatReturnsMultipleValues()};
...this. Here, I have to inspect FuncThatReturnsMultipleValues to know
that it returns multiple values. I object to that. I strongly would
prefer to require some syntax to indicate unpacking, e.g. '[*]' or '...'
or whatever.
> Well, the ABI now has to include the "std::value_sequence" type, which
> again is a "magic type", not a normal type. I admittedly know very little
> about ABIs, so I have no idea how any particular "magic type" of this sort
> would be handled at the ABI level. But it would depend on how much "magic"
> is in this magic type.
>
> That being said, and given that I don't know much about ABIs, I don't
> really understand why MRVs would be problematic for ABIs. Or more to the
> point, how the solution of returning a "magic value" would differ from the
> MRV solution.
I don't think there is much magic, and I think we are more or less
wanting the same thing. The only real exception that I am seeing is that
unpacking and MRV's (and expansion) are orthogonal, and that an MRV has
a type.
Note: the more I think about it, the more I'm inclined to say that it is
an unnameable type, like lambdas. That is, 'auto x = mrv_func()' is
legal, but doing anything besides unpacking 'x' is not. (Potentially
even passing it via a template type.)
> In both cases, the compiler needs to know how much space to
> allocate, which will be based on some aggregation of the types involved. In
> both cases, the compiler and it's ABI will have to define that aggregation,
> whether it's in the "magic type" (and therefore, the ABI will have to
> define the ordering of the items in the type as well as their layout) or
> purely internally (with the ABI defining the offsets/alignment for each
> type in the returned data).
Right, though I don't think there is a real difference here.
Particularly if the type is unnameable, the order isn't necessarily
consistent for a given type list, but would depend on the callee. (In
neither case, AFAIK, can it vary at run-time, or by who is the caller.)
>>> Because syntax is rare and precious, it is not cheap. And valuable
>>> syntax should not be created just because it makes the lives of
>>> generic programmers who work with tuples easier.
Incidentally, I'm not sure this is a *valid* reason. Depending on the
unpacking syntax, knowing that you are unpacking without a lot of
look-ahead might be difficult. Compiler vendors tend to not like that;
as a result, it may be that your unpacking syntax would not be any more
context sensitive than generic unpacking.
Agreed, but I also consider parameter expansion an orthogonal problem.
>> unpacking has potential value for any
>> sequence-like structure. Think about arrays, points, ranges,
>> dimensions... all could potentially use unpacking.
>
> I cannot recall a single instance where I've had the desire to "unpack"
> members of an array, point, or arbitrary ranges into a function call or
> anything of the like.
I can. Here's the example (slightly simplified, and with names changed)
that I know of offhand:
inline void foo(point2d const& in, double result[2])
{
// Method 1 (shorter)
[result[0]; result[1]] = foo([*]in);
// Method 2 (more generic)
std::tie([*]result) = std::tie([*](foo[*]in));
// C++98 (not generic)
const point2d p = foo(in.X, in.Y);
result[0] = p.X;
result[1] = p.Y;
}
This is in real, production code, and yes I would write it like that if
I could. (Note: I'm not sure if assignment like that actually works, but
that's only an issue because my 'point2d' is not assignable from a tuple.)
Unpacking objects (usually geometry types) into individual local
variables is not unusual for me.
double result[2];
to_double2(foo(data), result);
//Unpack pair
std::pair<int,int> p;
[const auto& p0, const auto& p1] = p;
//Unpack tuple
std::tuple<int, int, int> t;
int t2;
[const auto& t0; void; t2] = t;
//Unpack C array
int a[2];
[auto a0, auto a1] = a;
//Unpack std::array
std::array<int,4> ax;
[auto ax0; auto ax1, void, void] = ax;
//Unpack string literal
std::string_literal<3> s = "bar";
[auto sb; auto sa; auto sr] = s;
//Braced initialization can be unpacked
[auto i; auto l; auto d] = { 1, 1L, 1.0 };
[int,float,double] foo();
auto... x = foo();
//More verbose, obvious where parameter packs are used
[auto x, auto y, auto z] = { x... }; //implemented using braced initializer support
[auto x1, void, void] = { foo()... };
std::max_element({ mrvs... });
std::forward<decltype(mrvs)>(mrvs)...;
[int,float,double] foo();
auto... x = foo();
//More terse, possible confusion is x a type or
[auto x, auto y, auto z] = x;
[auto x1, void, void] = foo();
[int,float,double] foo();
void bar(int, float, double);
template <typename... Args> void run(Args&&... tpp) {
auto... mrvs = foo();
bar(mrvs);
bar(tpp...);
}
//Operator returns an MRV sequence
class udt0 {
public:
constexpr operator[auto&&...]() = default;
//constexpr operator[auto...]() { return {i, f, d}; }
//constexpr operator[auto...]() { return {f, d, i}; }
//constexpr operator[int,float,double]() { return {i,f,d}; };
//constexpr operator[double,float,int]() { return {d, f, i}; };
//constexpr operator[double,auto]() { return {d - i, f + i}; };
private:
int i; float f; double d;
} udt;
[auto di; auto fi] = udt;
//POD types have compiler generated operator[auto...]
struct kv { string key; vector<int> value; } kv;
struct Foo { Foo(int, double); };
struct Bar { Bar(const Foo&, const Foo&); }
auto... x = { 1, 1.0 };
auto f = Foo{x};
auto b = Bar{{x}, {x}}
//Expands to 2 mrv packs which are then forwarded to BAR(const Foo&, const Foo&)
std::make_unique<Bar>({1, 1.0}, {2, 2.0});
On 2015-05-28 18:51, Nicol Bolas wrote:
> On Thursday, May 28, 2015 at 5:22:42 PM UTC-4, Matthew Woehlke wrote:
>> What I object to is:
>>
>>> auto x = Typename{FuncThatReturnsMultipleValues()};
>>
>> ...this. Here, I have to inspect FuncThatReturnsMultipleValues to know
>> that it returns multiple values. I object to that. I strongly would
>> prefer to require some syntax to indicate unpacking, e.g. '[*]' or '...'
>> or whatever.
>
> Why should using `FuncThatReturnsOneValue` require less syntax than
> `FuncThatReturnsMultipleValues`?
Because in no existing case can a single 'token' represent multiple
parameters without that being made obvious.
To ask the opposite question, should we allow this?
template <Typename Args...>
void foo(Args... args)
{
bar(args);
}
One could use a tuple, but this whole discussion started because you
objected (loudly) to the extraneous copies that would incur.
In the
above example, I might even hope that the compiler makes no copies
(excluding when calling 'bar', and possibly with some '&'s added). I
would *especially* expect no copies if I wasn't slicing the value
sequence, but merely reusing it.
auto x = get<0>(params);
> Um, I'm not talking about "unpacking syntax." Or rather, more to the point,
> I don't *want* "unpacking syntax". Or packing syntax.
>
> I want MRV syntax. Packing and unpacking would not be syntax; they'd just
> be function calls.
What, then, is your equivalent to this?
[auto x; auto y] = some_mrv_function();
> So your use case for this syntax is translating values of structs between
> different APIs?
That's *a* use case.
Here's another:
[auto x; auto y] = [*]p;
x = sin(2*x) + cos(2*y);
y = sqrt(y);
return {x, y};
Invented and somewhat simplified, but this is another case I run into
with moderate frequency ; given a "tuple-like" class (often one that does
not have public data members), I want to "crack" it into local variables
for various reasons; terseness, to avoid multiple invocations of
accessor methods, etc.
There is also, of course, the associative container iteration example.
I'm confident there are others I'm not thinking of offhand.
> In your example above, a user who wants the result as a `double[2]` should
> call `foo` like this:
>
> double result[2];
> to_double2(foo(data), result);
>
> That way, you don't have to write 20 different variations of `foo`.
I find it more convenient to write utility overloads to accept data in a
variety of formats, and I'm not the only API to do so. The difference is
I can write those overloads *once* and use them many times, compared to
having to write call-site conversions many times.
[int,int] f();
[int,int] g(int, int);
int h(int);
void i(int, int, int, int);
//Compose f and g and g again
g(g(f()...)...);
//Call h on each of f()'s return values and then forward the resulting sequence to g().
g(h(f())...);
//f() produces 2 values, call g() on each of them separately to produce 4 values which is then forwarded to i().
i(g(f())...);
One could use a tuple, but this whole discussion started because you
objected (loudly) to the extraneous copies that would incur.
Then you've misunderstood the point.
The primary justification for this feature was having multiple return values. It's the title of the thread. My point was that, by doing it as proposed (through std::tuple), it interferes with elision. Which it does.
Remember: the elision problem was that the callee had some variable, which was being packed into a tuple, then unpacked into the caller's variable. The elision in question was the elision between the callee's variable and the caller's variable.
Well, if the caller is storing the tuple as a tuple, rather than broadcasting the tuple elements into variables, this argument no longer applies.
In the
above example, I might even hope that the compiler makes no copies
(excluding when calling 'bar', and possibly with some '&'s added). I
would *especially* expect no copies if I wasn't slicing the value
sequence, but merely reusing it.
As previously stated, the standard requires this statement:
auto x = get<0>(params);
to call a constructor. Elision works only in cases where elision is expressly permitted. And this is in no way one of them.
Not only that, elision in this case breaks all kinds of C++ guarantees. In your case, if the copy/move was elided, `x` and `params` would be sharing data.
[int,float] f();
[auto x; auto y] = f();
auto xy = f();
[auto x; auto y]=xy;
On 2015-05-29 22:59, Matthew Fioravante wrote:
>
>> On Friday, May 29, 2015 at 11:16:56 AM UTC-4, Matthew Woehlke wrote:
>>> Here's another [use case]:
>>>
>>> [auto x; auto y] = [*]p;
>>> x = sin(2*x) + cos(2*y);
>>> y = sqrt(y);
>>> return {x, y};
>>
>> If you violate a class's interface, you are *committing fraud*. You had
>> an agreement with the class, that you would follow its public interface.
>> And you are knowingly and deliberately breaking that agreement.
>
> Obviously compiler generated unpacking would only be allowed for standard
> layout types with all public data members. If you want to support unpacking
> for a class with private data members you'll have to write the operator
> yourself.
Er... right. Nicol, you're adding scary things to my proposal that *are
not part of it*.
On 2015-05-29 19:57, Nicol Bolas wrote:
> On Friday, May 29, 2015 at 11:16:56 AM UTC-4, Matthew Woehlke wrote:
>> On 2015-05-28 18:51, Nicol Bolas wrote:
>>> Why should using `FuncThatReturnsOneValue` require less syntax than
>>> `FuncThatReturnsMultipleValues`?
>>
>> Because in no existing case can a single 'token' represent multiple
>> parameters without that being made obvious.
>
> ... what?
>
> `FuncThatReturnsMultipleValues()` is not a "token"; it's *three* tokens.
> It's an identifier, followed by an open paren, followed by a close paren.
(OT: Do you have *any idea* how annoying it is when you do this? I'm
trying to talk in abstract concepts, okay? It's frustratingly difficult
to do that when a) all the terminology that naturally lends itself to
doing so has already been "coopted" to have some specific meaning in
standardese, which b) you must insist on applying.)
> If by "token", you meant "expression", then you're correct.
...and last time I used "expression" you complained at me for that too.
> The result of an expression is, without multiple return values, a
> single value with a single type. Therefore, in order for MRVs to
> work, that will have to change.
Yes, it would have to change. That is a big part of my objection.
>> One could use a tuple, but this whole discussion started because you
>> objected (loudly) to the extraneous copies that would incur.
>
> Then you've misunderstood the point.
>
> The primary justification for this feature was having multiple return
> values. It's *the title of the thread*.
Yes, but the *feature* was *multiple assignments*. (That's *also* in the
title of the thread. First, in fact.) AFAICT you are trying to kill, or
needlessly cripple, that.
>> What, then, is your equivalent to this?
>>
>> [auto x; auto y] = some_mrv_function();
>
> Under MRV rules, that is not "unpacking" or "expansion", because
> `some_mrv_function` does not return a "packed" value that needs to be
> "unpacked" or "expanded". It returns *multiple values*.
>
> Under MRV, this is merely the syntax for storing multiple values.
So, you are saying you *still* want unpacking syntax¹, you just only
want it to work for MRV's? Why? What is the benefit to limiting it in
such manner? (As previously explained, I don't buy the "syntax is
precious" argument.)
On 2015-06-01 12:15, Nicol Bolas wrote:
> On Saturday, May 30, 2015 at 12:35:06 PM UTC-4, Matthew Woehlke wrote:
>> On 2015-05-29 19:57, Nicol Bolas wrote:
>>> The result of an expression is, without multiple return values, a
>>> single value with a single type. Therefore, in order for MRVs to
>>> work, that will have to change.
>>
>> Yes, it would have to change. That is a big part of my objection.
>
> ... why?
Because 'foo()' "looks like" a single semantic "thing". And right now it
always *means* a single semantic thing. You propose to make it possible
for it to suddenly mean *multiple* semantic things, with no indication
to the reader.
I want three things, all of which IMO can, and probably should, be
separate proposals:
1. Generic unpacking, i.e. multiple assignment from a "container".
2. A better (i.e. RVO-friendly) way to return multiple values without
the need to declare a struct for the purpose.
3. The ability to expand a "container" into multiple "values", e.g. as
function call arguments, or within a brace initializer list.
Each of these is made more valuable by the others, but each is also
useful by itself. None has an absolute dependency on another. (Not even
syntactically, I assert; see below.)
>> So, you are saying you *still* want unpacking syntax¹, you just only
>> want it to work for MRV's? Why? What is the benefit to limiting it in
>> such manner? (As previously explained, I don't buy the "syntax is
>> precious" argument.)
>
> You can choose not to buy it, but that doesn't make it false.
You are asking for this:
auto t = make_tuple(...);
[auto x; void; y] = foo(); // Legal
[auto a; void; b] = t; // Illegal / reserved for future use
Really? Leaving aside the compiler complexity arguments, I'd be inclined
to reject that just on the basis of the confusion it is likely to cause.
Context-sensitive syntax has a high cognitive burden. That is much less
an issue for context-sensitive keywords. (For one, most people will
pretend that they are just keywords, and not use them otherwise.)
As a code writer, I look at the above and want to know why the second
one doesn't work.