Multiple return values

402 views
Skip to first unread message

Hariharan Subramanian

unread,
Apr 29, 2013, 10:03:16 AM4/29/13
to std-pr...@isocpp.org
Problem:
Having a single return value means we have to pass the other return values as parameters as a reference or a pointer. This makes the programs difficult to understand. Moreover we can solve most of the problems mentioned in http://www.openstd.org/JTC1/SC22/WG21/docs/papers/2013/n3538.html

Solution:
(int; float) foo (int);
Probably a contextual keyword returns can be added to specify that the first pair of parentheses represents return values. So we will be passing only in parameters and in out parameters to the function. All the out parameters are only returned.

With this we can write the code

int a;
float b;
(a; b) = foo(a, b);
(int c; float d) = foo(a, b);

If foo returns int & instead of int then we need to write

(int &c; float d) = foo(a, b);

If we decide to ignore some return values we can use void for those return values.

We can pass the return values to functions. We can write foo( foo(a, b) );

We can compare the return values. The semantics can be defined as the operation should be true for all the return values.
foo(a,  b) == foo(c, d), foo(a, b) >= foo(c, d) etc.,

We cab do the following also
(c, d) == foo(a, b), (c, d) > foo(a, b) etc.,

(a, b) = (b, a) swaps the values. It should generate the code equivalent to

(float; int) dummy(float b, int a) {
     return (b, a);
}
(a, b) = dummy(b, a);

Move semantics can be used to assign. Hence we will not face the issue mentioned in N3538 document if there are no in out parameters.

There are some problem areas like type casting. Probably they can be prohibited. Other issues may remain and need to be discussed.

Probably multiple return values might have been discussed earlier. Nevertheless, I wanted to give it a shot.

Ville Voutilainen

unread,
Apr 29, 2013, 10:04:54 AM4/29/13
to std-pr...@isocpp.org
On 29 April 2013 17:03, Hariharan Subramanian <toh...@gmail.com> wrote:
Problem:
Having a single return value means we have to pass the other return values as parameters as a reference or a pointer. This


Return a tuple.

Now, reconsider whether you still want to make a core language change. ;)

DeadMG

unread,
Apr 29, 2013, 10:09:41 AM4/29/13
to std-pr...@isocpp.org
He has a valid point, I think. Returning tuples and especially unpacking on the other end isn't as easy as it could be in some languages with core language support for this- for example, Lua.

Ville Voutilainen

unread,
Apr 29, 2013, 10:15:46 AM4/29/13
to std-pr...@isocpp.org
On 29 April 2013 17:09, DeadMG <wolfei...@gmail.com> wrote:
He has a valid point, I think. Returning tuples and especially unpacking on the other end isn't as easy as it could be in some languages with core language support for this- for example, Lua.




Perhaps so. I'm just pointing out in a rather friendly manner that adding such core language support may end
up being an uphill battle.

DeadMG

unread,
Apr 29, 2013, 10:19:45 AM4/29/13
to std-pr...@isocpp.org
I agree. I'm not saying that there is a core feature that will work for C++, since language-level support for tuples covers a whole lot more than his simple use case (although it could, potentially, be used to fix the very broken uniform initialization). What I am saying is that it's not a problem which is entirely solved by std::tuple, and it would be a desirable feature to have.

Christopher Jefferson

unread,
Apr 29, 2013, 11:33:22 AM4/29/13
to std-pr...@isocpp.org
On 29 April 2013 15:03, Hariharan Subramanian <toh...@gmail.com> wrote:
Problem:
Having a single return value means we have to pass the other return values as parameters as a reference or a pointer. This makes the programs difficult to understand. Moreover we can solve most of the problems mentioned in http://www.openstd.org/JTC1/SC22/WG21/docs/papers/2013/n3538.html

I decided to have a go at seeing how much of this could be done with std::tuple. 

Solution:
(int; float) foo (int);
std::tuple<int, float> foo(int); 
Probably a contextual keyword returns can be added to specify that the first pair of parentheses represents return values. So we will be passing only in parameters and in out parameters to the function. All the out parameters are only returned.

With this we can write the code

int a;
float b;
(a; b) = foo(a, b);
std::tie(a,b) = foo(a,b); 
(int c; float d) = foo(a, b);
This one we have to write:
int c; float d;
std::tie(c, d) = foo(a,b); 

We can pass the return values to functions. We can write foo( foo(a, b) );
Now you are automatically unpacking tuples. What happens if foo takes a tuple? This case doesn't work, but you have to be careful. 

We can compare the return values. The semantics can be defined as the operation should be true for all the return values.
foo(a,  b) == foo(c, d), foo(a, b) >= foo(c, d) etc.,
Works with std::tuple already

We cab do the following also
(c, d) == foo(a, b), (c, d) > foo(a, b) etc.,
std::tie(c,d) == foo(a,b), std::tie(c,d) > foo(a,b); 

(a, b) = (b, a) swaps the values. It should generate the code equivalent to
This one, I wouldn't be confident doing with tie (but it might work). However, we have swap :) 

 
There are some problem areas like type casting. Probably they can be prohibited. Other issues may remain and need to be discussed.

The main problem (as I outline in my mail) is that many of these things can be done with std::tuple and std::tie. If this is just syntatic sugar over std::tuple and std::tie, then is it worth it, and if it is a seperate kind of tuple, we have to worry about the two interacting.

Hariharan Subramanian

unread,
Apr 30, 2013, 1:06:23 AM4/30/13
to std-pr...@isocpp.org
I wrote the proposal without considering the suggested alternatives. Hence I would like to withdraw this proposal. Thanks for suggesting the alternatives.

Hariharan S


On Mon, Apr 29, 2013 at 7:49 PM, DeadMG <wolfei...@gmail.com> wrote:
I agree. I'm not saying that there is a core feature that will work for C++, since language-level support for tuples covers a whole lot more than his simple use case (although it could, potentially, be used to fix the very broken uniform initialization). What I am saying is that it's not a problem which is entirely solved by std::tuple, and it would be a desirable feature to have.

--
 
---
You received this message because you are subscribed to a topic in the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this topic, visit https://groups.google.com/a/isocpp.org/d/topic/std-proposals/VROimyg83fc/unsubscribe?hl=en.
To unsubscribe from this group and all its topics, send an email to std-proposal...@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/?hl=en.
 
 

morw...@gmail.com

unread,
Apr 30, 2013, 7:07:51 AM4/30/13
to std-pr...@isocpp.org
If you want to think about core tuples in a C-like programming language, you better have a look at how it was done in Alef first:
http://en.wikipedia.org/wiki/Alef_%28programming_language%29

And we can also think at how (1, 2) is a tuple in Python also, (1) is not, but (1,) is and try to think about all the problems linked to parsing this kind of tuples in C++;

unituni...@gmail.com

unread,
May 7, 2013, 4:33:17 PM5/7/13
to std-pr...@isocpp.org, morw...@gmail.com
it's so hard to implement what you asked into C++ than you thought. To C, it's may be much easier.
What the function returns must be a ensured Type.
    Then what the type of (int c, fload d)?
You can't just ignore this question because the templates decoding and something else need it.
Say:
 auto retval = func(a, b); // What's the type of retval?
 decltype(retval) val2(retval); // Even you can give a hidden type to retval, what should happen at this line??
val2 = retval;// Even you can give a hidden type to retval, what should happen at this line??
typedef decltype(func(a,b)) T1; // What the T1 should be?
You can't treat the func as a special kind of function simply, because the function will be passed to templates, many rules should be modified.

Tony V E

unread,
May 10, 2013, 1:16:44 PM5/10/13
to std-pr...@isocpp.org

--
 

Not sure how feasible it is, but I'd love to use tuples for all of this, with {a,b,c} syntax to mean 'tuple'.

auto f() { return { 1, 2, "three" }; }  // auto == tuple<int,int,char const *>

int a, b;
char const * c;

{ a, b, c } = f();  // works like std::tie()

etc.

Not sure about the best way of declaring a,b,c within the brackets.

{ int a; }

is already valid syntax.

 

Richard Smith

unread,
May 10, 2013, 2:48:58 PM5/10/13
to std-pr...@isocpp.org
An expression starting with '{' is certainly problematic. The problem here isn't so much how to parse it (that actually turns out to be tractable with basically no overhead and no extra lookahead) but more that it seriously harms diagnostics and error recovery in some important cases. There are some genuinely ambiguous cases, too, if we allow the general form:

  {} + x; // empty compound-statement followed by unary operator, or binary operator with empty braced-init-list on LHS?

Using parens, as the original proposal suggests, may avoid this particular problem, but I strongly suspect there will still be ambiguities.


I've been experimenting with a different approach to this problem: allow a restricted form of simple-declaration as an lvalue expression, then:

  std::tie(int a, int b, const char *c) = f();

This also lessens the need to use tuples at all, since you can write out-parameters like this:

  f(int a, int b, const char *c);

This still has some harmful impact on error recovery (the above could be a declaration of 'f' missing a return type, for instance), and it requires restricting the declarator forms to avoid declaration/expression ambiguity for cases like 'f(int(a))'.

Both this and the "{ a, b, c } = " form have an additional (potentially significant) problem: they require a, b, and c to be default-constructible and move-assignable.


Given the alternatives in the design space, returning a tuple and capturing it with 'auto' seems reasonable to me, aside from the syntactic inconvenience of accessing a tuple element. If the caller needs names for the fields, maybe returning a tuple with unnamed fields was a poor design choice, and returning a struct would be a better option?

Tony V E

unread,
May 10, 2013, 10:24:21 PM5/10/13
to std-pr...@isocpp.org
On Fri, May 10, 2013 at 2:48 PM, Richard Smith <ric...@metafoo.co.uk> wrote:


An expression starting with '{' is certainly problematic. The problem here isn't so much how to parse it (that actually turns out to be tractable with basically no overhead and no extra lookahead) but more that it seriously harms diagnostics and error recovery in some important cases. There are some genuinely ambiguous cases, too, if we allow the general form:

  {} + x; // empty compound-statement followed by unary operator, or binary operator with empty braced-init-list on LHS?


I'd like to pretend that is:

   std::tuple<>() + x;

 
Using parens, as the original proposal suggests, may avoid this particular problem, but I strongly suspect there will still be ambiguities.


We could always use some new parens.  ie {: a, b, c :}  or happy faces or other emoticons.
 

I've been experimenting with a different approach to this problem: allow a restricted form of simple-declaration as an lvalue expression, then:

  std::tie(int a, int b, const char *c) = f();

This also lessens the need to use tuples at all, since you can write out-parameters like this:

  f(int a, int b, const char *c);


That doesn't look very out-ish to me at first glance.

 
This still has some harmful impact on error recovery (the above could be a declaration of 'f' missing a return type, for instance), and it requires restricting the declarator forms to avoid declaration/expression ambiguity for cases like 'f(int(a))'.

Both this and the "{ a, b, c } = " form have an additional (potentially significant) problem: they require a, b, and c to be default-constructible and move-assignable.


If they are in fact tuples/ties.


Given the alternatives in the design space, returning a tuple and capturing it with 'auto' seems reasonable to me, aside from the syntactic inconvenience of accessing a tuple element. If the caller needs names for the fields, maybe returning a tuple with unnamed fields was a poor design choice, and returning a struct would be a better option?

--
 

enisbay...@gmail.com

unread,
Jul 12, 2013, 8:04:16 AM7/12/13
to std-pr...@isocpp.org
I usually don't have a problem with writing:

int a; double b;
tie(a,b) = f();

And I really like using tuples this way, but (to me) there are two cases where the tuples as an alternative to multiple return values. (1) when the types in the tuple are hard to write, (2) at the beginning of a "for" loop:

(1)
decltype(f().first) a; decltype(f().second b; // and possibly the list goes on
tie(a,b) = f(); 

(2)
int a; double b; // Note that these variables are outside the scope of the for loop, so that's not the same thing
for(tie(a,b)=f(); a<b;a++) ... 

The alternative to (2) to avoid a and b from spilling over to the enclosing scope it the following:

{
   int a; double b; 
   for(tie(a,b)=f(); a<b;a++) {
      // Do stuff
   }
}

Which is rather ugly.
Reply all
Reply to author
Forward
0 new messages