decltype(return)

546 views
Skip to first unread message

Avi Kivity

unread,
Nov 18, 2016, 11:01:41 AM11/18/16
to std-pr...@isocpp.org

map<foo, map<set<bar>, baz>
complicated(...) {
decltype(return) ret;
// add stuff to ret
return try;
}

Instead of repeating the complicated type, refer to it via
decltype(return). Won't work with deduced return types, of course.

Matt Calabrese

unread,
Nov 18, 2016, 1:06:17 PM11/18/16
to ISO C++ Standard - Future Proposals
On Fri, Nov 18, 2016 at 11:01 AM, Avi Kivity <a...@scylladb.com> wrote:

map<foo, map<set<bar>, baz>
complicated(...) {
    decltype(return) ret;
    // add stuff to ret
    return try;
}

I like this a lot. 

D. B.

unread,
Nov 18, 2016, 2:54:24 PM11/18/16
to std-pr...@isocpp.org
Sounds like a nice idea, but can you elaborate a little, maybe with some more examples? I'm most confused about the "return try" part of this.

Avi Kivity

unread,
Nov 18, 2016, 3:04:17 PM11/18/16
to std-pr...@isocpp.org
On 11/18/2016 09:54 PM, D. B. wrote:
Sounds like a nice idea, but can you elaborate a little, maybe with some more examples? I'm most confused about the "return try" part of this.

I'm sorry, I meant "return ret".  "return try" is maybe part of another proposal :) we have lots of keyword permutations to play with.

To make this a little more formal,

   Within the body and parameter list of a function that does not have deduced return types:

       decltype(return)

   is the same type as the function's return type.

Example use:


    map<foo, map<set<bar>, baz>>
    with_another_element(const decltype(return)& in, foo new_element) {
        decltype(return) ret;
        ret.insert({new_element, {}});
        return ret;
    }

is equivalent to


    map<foo, map<set<bar>, baz>>
    with_another_element(const map<foo, map<set<bar>, baz>>& in, foo new_element) {
        map<foo, map<set<bar>, baz>> ret;
        ret.insert({new_element, {}});
        return ret;
    }


--
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.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/CACGiwhFY8C30LKtApxjopXDJKH-hMrrhaZdL66moW2xf4fUCHw%40mail.gmail.com.


Matthew Woehlke

unread,
Nov 18, 2016, 3:18:57 PM11/18/16
to std-pr...@isocpp.org
On 2016-11-18 15:04, Avi Kivity wrote:
> To make this a little more formal,
>
> Within the body and parameter list of a function that does not have
> deduced return types:

I'm not sure if this restriction is necessary; it might be okay to relax
it to 'as long as the return type has been deduced before
`decltype(return)` is used'.

--
Matthew

Avi Kivity

unread,
Nov 18, 2016, 3:23:25 PM11/18/16
to std-pr...@isocpp.org
Yes, that works too, and is useful.


Andrzej Krzemieński

unread,
Nov 23, 2016, 4:50:46 AM11/23/16
to ISO C++ Standard - Future Proposals

In your example, you use this hypothetical feature to create up front the value you will be returning. I wonder if this is the only use case that drives your feature, because if so, you could consider an alternative feature: give a variable name in the place where you declare the return type:

auto complicated(...) -> map<foo, map<set<bar>, baz> ret // ret is a "return variable"
{
 
// add stuff to ret
} // no return statement is ok: we have an implicit return variable

Of course, your solution is more general, it also covers other cases:

map<foo, map<set<bar>, baz>
complicated(...)
{
  decltype(return) a, b;
  // populate a and b
  if (is_better(a, b))
    return a;
  else
    return b;
}

Regards,
&rzej;

D. B.

unread,
Nov 23, 2016, 4:53:45 AM11/23/16
to std-pr...@isocpp.org
On Wed, Nov 23, 2016 at 9:50 AM, Andrzej Krzemieński <akrz...@gmail.com> wrote:

auto complicated(...) -> map<foo, map<set<bar>, baz> ret // ret is a "return variable"
{
 
// add stuff to ret
} // no return statement is ok: we have an implicit return variable


[bikeshed] I don't find this very appealing. It seems prone to errors and is inapplicable for functions needing early return anyway. [btw, I don't even like the implicit return 0; in main() very much]

Andrzej Krzemieński

unread,
Nov 23, 2016, 6:56:45 AM11/23/16
to ISO C++ Standard - Future Proposals

Just two comments, now that you have explored this.
  1. Having an implicit return value does not prevent you from explicitly returning something else. 
  2. This solution is less implicit than the case of main, because you have to explicitly say that you are using an implicit return value.

But you may be right, that this might turn out to be prone to errors. Without playing with it, one never knows.


Regards,

&rzej;

D. B.

unread,
Nov 23, 2016, 7:16:41 AM11/23/16
to std-pr...@isocpp.org

On Wed, Nov 23, 2016 at 11:56 AM, Andrzej Krzemieński <akrz...@gmail.com> wrote:

Just two comments, now that you have explored this.
  1. Having an implicit return value does not prevent you from explicitly returning something else. 
  2. This solution is less implicit than the case of main, because you have to explicitly say that you are using an implicit return value.

But you may be right, that this might turn out to be prone to errors. Without playing with it, one never knows.


Regards,

&rzej;

 
Indeed, maybe it's not so bad and could be useful. While I can't think of many uses for it personally, it would actually be interesting to see it explored further, like some use cases where it could help a lot.

This idea reminded me of Visual Basic, where a function would return the value that was most recently assigned to its own name... but having a separate name or a specific keyword is probably better or even required in C++, as this would avoid (somewhat contrived) ambiguities with e.g. function pointers, recursion, etc.

Did you envision that the return variable could be called anything, or is "ret" intended to be a sort of keyword here?

Andrzej Krzemieński

unread,
Nov 23, 2016, 7:32:24 AM11/23/16
to ISO C++ Standard - Future Proposals

No keyword. You pick the name for it, as for any other variable. This has been already implemented in Go: https://tour.golang.org/basics/7

Regards,
&rzej;

Nicol Bolas

unread,
Nov 23, 2016, 10:47:03 AM11/23/16
to ISO C++ Standard - Future Proposals


On Wednesday, November 23, 2016 at 6:56:45 AM UTC-5, Andrzej Krzemieński wrote:


W dniu środa, 23 listopada 2016 10:53:45 UTC+1 użytkownik D. B. napisał:
On Wed, Nov 23, 2016 at 9:50 AM, Andrzej Krzemieński <akrz...@gmail.com> wrote:

auto complicated(...) -> map<foo, map<set<bar>, baz> ret // ret is a "return variable"
{
 
// add stuff to ret
} // no return statement is ok: we have an implicit return variable


[bikeshed] I don't find this very appealing. It seems prone to errors and is inapplicable for functions needing early return anyway. [btw, I don't even like the implicit return 0; in main() very much]

Just two comments, now that you have explored this.
  1. Having an implicit return value does not prevent you from explicitly returning something else. 
This is getting a bit off-topic (since there are plenty of uses for `decltype(return)` besides declaring a variable to be returned), but...

C++'s function model gives every function a return value object. As it currently stands, this object is initialized by the `return` statement, but the storage for it always exists (provided by the caller).

If you can name this variable, then it seems to me that what you're doing is giving the user direct access to the return value object itself. This named return value object is being initialized at the start of the function, and the user gets to manipulate it in whatever way they see fit. If you are directly manipulating the return value object, it must be impossible for a user to return "something else".

Oh sure, a return statement in a function with a named return value (NRV) could simply perform the equivalent of assigning the returned expression to the NRV. Or even destroying the existing NRV object and re-creating it in-situ (which is perfectly legal C++). But you aren't truly returning "something else"; you're just doing what a typical `return` statement would do.

There are two things I don't like about directly accessing the return value object the way you define it is this.

1: Where it gets named.

You name it in the function's signature. Why? Users of the function don't need to know the name. Users of the function don't even need to know that it has a name. I suppose you could treat the name as optional, the way parameter names are optional. But I don't think function signatures need more optional things.

2: Where it gets initialized.

With your way, it must be initialized at the start of the function, before the first `{`. A `return` statement may overwrite it, but since it has a name, it must be an initialized variable. This means that you would have to invoke a lambda to do more complex initialization of the return variable. Even worse, catching exceptions would be a big pain.

What you really want is syntax to initialize the return value and get a non-const reference to it. This syntax can be invoked at any point in the function's execution. You should be able to do this more than once, with each time destroying the existing object if any and creating a new one.

Invoking `return expr` or `return {...}` statement would also re-initialize the return value, if it was already initialized. However, an empty `return;` statement will not re-initialize the return value object; it would simply returning the existing return value. If no object had been created, then UB results. Compilers can detect the trivial cases of you screwing this up and issue a warning, but obviously they can't detect them all.

This could combine well with P0057-style continuations, which plays games with the return value.

But in any case, the primary motivation for this kind of functionality is giving us guaranteed named return value elision.

Avi Kivity

unread,
Nov 23, 2016, 11:36:56 AM11/23/16
to std-pr...@isocpp.org
It seems like this was favorably received. How do I make progress with
this? Is there a proposal genie I can bribe to turn it into a paper?
Or at least a proposal template I can fill in?

I'm afraid I won't be able to present it in person, but I'm willing to
work on wording (to my limited ability in the matter) if someone will
pick it up and present it.

Erich Keane

unread,
Nov 23, 2016, 5:18:41 PM11/23/16
to ISO C++ Standard - Future Proposals
Generally you'd need to write a paper for EWG.  It is one that we would then look at, though unless there is someone at the meeting 'pushing' for it, it likely wouldn't get considered.  Also, it becomes less likely in the next meeting or two solely due C++17 nearly finalized, but it is something that could hang around for C++2x

I think I'll be spending some time in Evolution, but if I get a bit of time, perhaps I can write something up.

Magnus Fromreide

unread,
Nov 23, 2016, 7:02:06 PM11/23/16
to std-pr...@isocpp.org
I will pick up on the bikeshed here.

In GCC before 3.4 there was an "named return value extension" that looks
pretty similar to the one proposed here, they used the syntax

map<foo, map<set<bar>, baz>
complicated(...)
return ret
{
// add stuff to ret
return ret;
}

Should this example of earlier implementation be allowed to influence the
design?

See also https://gcc.gnu.org/onlinedocs/gcc-2.95.3/gcc_5.html#SEC106

/MF

Erich Keane

unread,
Nov 23, 2016, 8:38:09 PM11/23/16
to ISO C++ Standard - Future Proposals

The syntax you propose could be extremely expensive, since it would require default constructing the variable, and then mutating it.  Of course, if the mutation isn't permissible (such as in an immutable type), this would fail.

Andrzej Krzemieński

unread,
Nov 24, 2016, 3:29:22 AM11/24/16
to ISO C++ Standard - Future Proposals


W dniu środa, 23 listopada 2016 16:47:03 UTC+1 użytkownik Nicol Bolas napisał:


On Wednesday, November 23, 2016 at 6:56:45 AM UTC-5, Andrzej Krzemieński wrote:


W dniu środa, 23 listopada 2016 10:53:45 UTC+1 użytkownik D. B. napisał:
On Wed, Nov 23, 2016 at 9:50 AM, Andrzej Krzemieński <akrz...@gmail.com> wrote:

auto complicated(...) -> map<foo, map<set<bar>, baz> ret // ret is a "return variable"
{
 
// add stuff to ret
} // no return statement is ok: we have an implicit return variable


[bikeshed] I don't find this very appealing. It seems prone to errors and is inapplicable for functions needing early return anyway. [btw, I don't even like the implicit return 0; in main() very much]

Just two comments, now that you have explored this.
  1. Having an implicit return value does not prevent you from explicitly returning something else. 
This is getting a bit off-topic (since there are plenty of uses for `decltype(return)` besides declaring a variable to be returned), but...

C++'s function model gives every function a return value object. As it currently stands, this object is initialized by the `return` statement, but the storage for it always exists (provided by the caller).

If you can name this variable, then it seems to me that what you're doing is giving the user direct access to the return value object itself. This named return value object is being initialized at the start of the function, and the user gets to manipulate it in whatever way they see fit. If you are directly manipulating the return value object, it must be impossible for a user to return "something else".

Oh sure, a return statement in a function with a named return value (NRV) could simply perform the equivalent of assigning the returned expression to the NRV. Or even destroying the existing NRV object and re-creating it in-situ (which is perfectly legal C++). But you aren't truly returning "something else"; you're just doing what a typical `return` statement would do.

There are two things I don't like about directly accessing the return value object the way you define it is this.

1: Where it gets named.

You name it in the function's signature. Why? Users of the function don't need to know the name. Users of the function don't even need to know that it has a name. I suppose you could treat the name as optional, the way parameter names are optional. But I don't think function signatures need more optional things.

Just to clarify a couple of points on this "vision". Such implicit return variable is not part of function signature. Function separated into a declaration and a definition would look like this:

// declaration:
map<foo, map<set<bar>, baz> complicated(...);

// definition:

auto
complicated(...) -> map<foo, map<set<bar>, baz> ret {baz{}} // return value is initialized

{
 
// add stuff to ret
}

This is similar to taking an int by value in a declaration and by const value in the definition:

void fun(int x);      // declaration
void fun(const int x) // definition
{}

Also, as shown in the example above there is a way to initialize te return variable with an arbitrary initialization.

Regards,
&rzej;


 
 

Andrzej Krzemieński

unread,
Nov 24, 2016, 3:32:04 AM11/24/16
to ISO C++ Standard - Future Proposals

Not necessarily. I have explained it in another reply. But this is your thread with your proposal. My main goal with this is to encourage you to come up with a detailed motivation, range of use cases you want to cover, comparison with other solutions, and rationale on why you chose your solution over others, like mine here.

Regards,
&rzej;

Tony V E

unread,
Nov 24, 2016, 4:38:29 PM11/24/16
to Standard Proposals
IMO, decltype(return) just looks better.
It is more similar to what we already have.
It doesn't clutter the function signature (which is getting cluttered with throw/noexcept, concepts/requires, contracts, ...)

--
Be seeing you,
Tony

Marc

unread,
Nov 24, 2016, 5:26:14 PM11/24/16
to ISO C++ Standard - Future Proposals

This has been suggested informally several times already, but afaik nobody did the work to write a paper and present it.
For instance:
https://groups.google.com/a/isocpp.org/d/topic/std-proposals/wdX31-nWSnc/discussion
(it also appears in the discussions about allowing explicit constructors in return statements)

Bjorn Reese

unread,
Nov 24, 2016, 5:42:47 PM11/24/16
to std-pr...@isocpp.org
On 11/24/2016 10:38 PM, Tony V E wrote:

> IMO, decltype(return) just looks better.
> It is more similar to what we already have.
> It doesn't clutter the function signature (which is getting cluttered
> with throw/noexcept, concepts/requires, contracts, ...)

And it can be used in more places, like

auto c = std::max<decltype(return)>(a, b);

Andrzej Krzemieński

unread,
Nov 24, 2016, 5:56:07 PM11/24/16
to ISO C++ Standard - Future Proposals
My experience shows that it is never one proposal, but a number of iterations: you write a proposal, you receive feedback, you write a revision, you receive a second feedback, and so on, until the proposal is mature enough. For the initial version wording may not be necessary, but it would be worth to consider more nasty cases. For instance:

struct X { void * p; }; // sizeof(X) == 8

std
::vector<X> fun()
{
   
struct X {}; // sizeof(X) == 1
   
decltype(return) v;
   
return v;
}

The question is, should decltype(return) be exactly the same type we have seen in function return type, or should it be a token-by token copy paste of the function's return type. Or in other owrds, should it work like this:

typedef std::vector<X> THE_TYPE;
THE_TYPE fun
()
{
    THE_TYPE v
;
}

or like this:

#define THE_TYPE std::vector<X>
THE_TYPE fun
()
{
    THE_TYPE v
;
}

We probably want the former, but it would be worth mentioning it explicitly, in case some implementation tries to do a token pastin.

Regards,
&rzej;  

Erich Keane

unread,
Nov 24, 2016, 6:02:26 PM11/24/16
to ISO C++ Standard - Future Proposals

That is a really compelling example, mind if I use it for the paper?

Erich Keane

unread,
Nov 24, 2016, 6:04:22 PM11/24/16
to ISO C++ Standard - Future Proposals

I would definitely think that the former behavior is the one most consistent with the language as it is.  This is something that proper wording would take care of, something like, "the type represented by dectype(return) is the actual return type of the containing function".  If this gets through EWG, I'll definitely keep this in mind writing hte Core paper.

szollos...@gmail.com

unread,
Nov 26, 2016, 8:36:36 AM11/26/16
to ISO C++ Standard - Future Proposals
Hi,
I like the idea very much, but please bear in mind that `return` is not a variable. It's a keyword and the closest approximation is an operator taking a single type, being [[noreturn]] and thus returning void. Thus, if you want to be coherent, decltype(return) should be `void(*)(T)` instead of `T`. Which is fine - you can write `paramof()` and then `decltype(paramof(return))`.
Why it's important (to me, at least)? I'm working on a way to capture `return` and pass it along. Double return, if you like: it's useful for custom control structures, like custom for-loop. If you put this piece in incoherently, I won't be able to specify the type of the `return` being captured (or at the very least, I'll need new logic for that).
With that said, I'd welcome a version of this in the standard.
/* As for the return variable: we have the need for uninitialized variables elsewhere (ideally even >> would be implemented that way), we have placement new() to initialize it. I don't really see a problem with taking the address of the variable-to-be-returned and then placement new-ing into it. While theoretically it'd limit optimization (for you're taking an address you didn't before), most compilers will probably eliminate the entire construct. I'd be okay with `[[uninitialized]] T t;` or `T __uninitialized t;` or even `std::space_for<T> spt; T& t = spt;`. */
Thanks,
-lorro
Thanks,
-lorro

Nicol Bolas

unread,
Nov 26, 2016, 11:03:22 AM11/26/16
to ISO C++ Standard - Future Proposals, szollos...@gmail.com
On Saturday, November 26, 2016 at 8:36:36 AM UTC-5, szollos...@gmail.com wrote:
Hi,
I like the idea very much, but please bear in mind that `return` is not a variable. It's a keyword and the closest approximation is an operator taking a single type, being [[noreturn]] and thus returning void.Thus, if you want to be coherent, decltype(return) should be `void(*)(T)` instead of `T`. Which is fine - you can write `paramof()` and then `decltype(paramof(return))`.

And yet, I don't think anyone will be in any way confused by the meaning of `decltype(return)`. Much as they're not confused by the idea of what `decltype(auto)` means.
 
Why it's important (to me, at least)? I'm working on a way to capture `return` and pass it along. Double return, if you like: it's useful for custom control structures, like custom for-loop. If you put this piece in incoherently, I won't be able to specify the type of the `return` being captured (or at the very least, I'll need new logic for that).

People will be more likely to need to get the type of the return value than they will need to create "custom control structures" that require getting the type of whatever you're capturing. So forcing people to use the long syntax for the more common case is not the best idea.
 
With that said, I'd welcome a version of this in the standard.
/* As for the return variable: we have the need for uninitialized variables elsewhere (ideally even >> would be implemented that way), we have placement new() to initialize it. I don't really see a problem with taking the address of the variable-to-be-returned and then placement new-ing into it.

The problem there is that such a feature must affect how the standard handles a `return` statement. If you initialize the return value object, then issue a `return <expr>`, the only reasonable thing for the compiler to do is destroy the return value object you initialized, then initialize it again with `<expr>`. To do anything else would impose requirements like copy/move assignment, as well as damage guaranteed elision.

As such, the compiler must have a way to know that you've initialized the return value. And that means it must be done via some specialized syntax, not merely getting some `void*`. Let's not make this harder on the compiler than it needs to be.

While I appreciate the need to solve the uninitialized variable problem generally, I think it is best to find a solution that uses higher-level, typed functionality, not encourage the passing around of `void*`s and using placement-`new`. We don't want to limit solutions to experts.

szollos...@gmail.com

unread,
Nov 29, 2016, 2:16:44 PM11/29/16
to ISO C++ Standard - Future Proposals, szollos...@gmail.com
Hi,


2016. november 26., szombat 17:03:22 UTC+1 időpontban Nicol Bolas a következőt írta:
On Saturday, November 26, 2016 at 8:36:36 AM UTC-5, szollos...@gmail.com wrote:
Hi,
I like the idea very much, but please bear in mind that `return` is not a variable. It's a keyword and the closest approximation is an operator taking a single type, being [[noreturn]] and thus returning void.Thus, if you want to be coherent, decltype(return) should be `void(*)(T)` instead of `T`. Which is fine - you can write `paramof()` and then `decltype(paramof(return))`.

And yet, I don't think anyone will be in any way confused by the meaning of `decltype(return)`. Much as they're not confused by the idea of what `decltype(auto)` means.
That's right. However,
 
Why it's important (to me, at least)? I'm working on a way to capture `return` and pass it along. Double return, if you like: it's useful for custom control structures, like custom for-loop. If you put this piece in incoherently, I won't be able to specify the type of the `return` being captured (or at the very least, I'll need new logic for that).

People will be more likely to need to get the type of the return value than they will need to create "custom control structures" that require getting the type of whatever you're capturing. So forcing people to use the long syntax for the more common case is not the best idea.
I'm unsure in this. I miss for_if / first / done / break / empty / for_map construct literally every week in actual work. Hence I started working on it.
 
With that said, I'd welcome a version of this in the standard.
/* As for the return variable: we have the need for uninitialized variables elsewhere (ideally even >> would be implemented that way), we have placement new() to initialize it. I don't really see a problem with taking the address of the variable-to-be-returned and then placement new-ing into it.

The problem there is that such a feature must affect how the standard handles a `return` statement. If you initialize the return value object, then issue a `return <expr>`, the only reasonable thing for the compiler to do is destroy the return value object you initialized, then initialize it again with `<expr>`. To do anything else would impose requirements like copy/move assignment, as well as damage guaranteed elision.
I expect to have either `return <expr>` or `new(&retval)T(expr)`. I'm not a big fan of default-constructed variables & assignment to them; still, if you need that, you can start your fn with `new(&retval)T()`. What brings a real difference is that now we know the address of the return value.

As such, the compiler must have a way to know that you've initialized the return value. And that means it must be done via some specialized syntax, not merely getting some `void*`. Let's not make this harder on the compiler than it needs to be.
Assume it's not initialized @ function entry and it must be initialized (by user) @ exit. Inbetween it's the programmer's responsibility to keep the usual rules.
Alternative: auto fn(T t) -> [[uninitialized]] T retval { new(&retval)T(t); }

Thanks,
-lorro

Matthew Woehlke

unread,
Dec 15, 2016, 1:40:54 PM12/15/16
to std-pr...@isocpp.org
Has anyone written or started working on a paper yet? I might be willing
to help if needed. I also might want to reference such paper...

On 2016-11-24 17:56, Andrzej Krzemieński wrote:
> it would be worth to consider more nasty cases. For instance:
>
> struct X { void * p; }; // sizeof(X) == 8
>
> std::vector<X> fun()
> {
> struct X {}; // sizeof(X) == 1
> decltype(return) v;
> return v;
> }
>
> The question is, should decltype(return) be exactly the same type we have
> seen in function return type, or should it be a token-by token copy paste
> of the function's return type.

Definitely the former! I've been working on a revised proposal to
replace P0222+P0224, which would make this a very important question to
address. To wit:

struct { int x; int y; } // note lack of name!
foo()
{
...
}

If we had *just* P0224 (and note that MSVC already allows the above!),
naming the return type is hard¹. The proposed `decltype(return)`
provides an obvious mechanism for naming the return type... provided
that's what it actually does.

(¹ You can do it, but it requires e.g. `decltype(foo())`. Not so bad in
this case with `foo` taking no arguments, but...)

I would argue that the second option - token-copying - is just *wrong*.
Especially in an example like the above, the resulting type would be
*different* from the function's return type, which is contrary to what
`decltype(return)` would appear to mean.

(On a related note, my revised version of P0222 actually uses
`decltype(return)` instead of `auto` as originally proposed.)

--
Matthew

Avi Kivity

unread,
Dec 19, 2016, 4:27:36 AM12/19/16
to std-pr...@isocpp.org, Erich Keane
On 12/15/2016 08:40 PM, Matthew Woehlke wrote:
> Has anyone written or started working on a paper yet? I might be willing
> to help if needed. I also might want to reference such paper...


Erich Keane (copied) had a draft.

Ivan G.

unread,
Dec 25, 2016, 8:33:58 AM12/25/16
to ISO C++ Standard - Future Proposals
I think it would be more useful to be able to get the full function type as `T(Args...)` and then extract the return type from it, as well as other information.
Currently it seems possible via decltype(functionName), but it is also dangerous because functionName must be correctly typed.

пятница, 18 ноября 2016 г., 19:01:41 UTC+3 пользователь Avi Kivity написал:

Antony Polukhin

unread,
Dec 25, 2016, 2:13:57 PM12/25/16
to std-pr...@isocpp.org
2016-11-18 19:01 GMT+03:00 Avi Kivity <a...@scylladb.com>:
>
> map<foo, map<set<bar>, baz>>
> complicated(...) {
> decltype(return) ret;
> // add stuff to ret
> return try;
> }
>
> Instead of repeating the complicated type, refer to it via decltype(return).
> Won't work with deduced return types, of course.

Syntax sugar makes the language harder to understand. It's not the
matter of decltype(return), it the matter of huge amount of proposals
that do small tunings and simplifications... and at one day we just
suddenly understand that the C++ language became close to Perl:

while (foo ~~ { @... }) {
return {} unless foo != bar;
foo += $&;
} else {
static decltype(return) var;
var += @ + ...;
++ g{hello}{word}[var];
var;
}
{}



decltype(return) does not reduce code lines, looks good only if the
function is small, motivates people to write ugly return types instead
of providing a humanreadable typedef for the result type:

using neuron_t = map<foo, map<set<bar>, baz>>;

neuron_t complicated(...) {
neuron_t ret;
// add stuff to ret
return try;
}

--
Best regards,
Antony Polukhin

Jonathan Müller

unread,
Dec 25, 2016, 2:20:59 PM12/25/16
to std-pr...@isocpp.org
```cpp
template <typename A, typename Lot, typename Of, typename Template.
typename Parameters>
using complicated_t = long_and_complicated<A, dependent_type<Lot, Of,
Template, Parameters>>;

template <typename A, typename Lot, typename Of, typename Template,
typename Parameters>
complicated_t<A, Lot, Of, Template, Parameters> complicated(…)
{
complicated_t<A, Lot, Of, Template, Parameters> ret;

return ret;
}



auto result = complicated(…);
```

Great, now I've added a template alias now caller is going to use thanks
to auto, but I still have to repeat a long name unnecessarily.

Nicol Bolas

unread,
Dec 25, 2016, 2:27:22 PM12/25/16
to ISO C++ Standard - Future Proposals


On Sunday, December 25, 2016 at 2:13:57 PM UTC-5, Antony Polukhin wrote:
2016-11-18 19:01 GMT+03:00 Avi Kivity <a...@scylladb.com>:
>
> map<foo, map<set<bar>, baz>>
> complicated(...) {
>     decltype(return) ret;
>     // add stuff to ret
>     return try;
> }
>
> Instead of repeating the complicated type, refer to it via decltype(return).
> Won't work with deduced return types, of course.

Syntax sugar makes the language harder to understand.

No, it doesn't. `for` loops are syntactic sugar for `while` loops. Range-based `for` loops are syntactic sugar for `for` loops over the begin/end iterators. And technically, you could implement all of those with `goto`.

Syntactic sugar is a good thing, when used appropriately. The question therefore is whether this is an appropriate use of it.

Considering that we already have the ability to automatically deduce the return type of a function based on the `return` statement, and the fact that we can use `auto` to prevent us from having to use the typename at the call site, `decltype(return)` is simply one more link in that chain.

We're trying to reduce pointless repetition.

decltype(return) does not reduce code lines,

That depends on how many lines it takes to enter that return type. After all, you can use `decltype(return)` even in a function that deduces its return type from a return statement. So that return type could be quite lengthy.
 
looks good only if the function is small,

This is also true of most `auto`-returning functions. Also, we want people to write small functions.
 
motivates people to write ugly return types instead of providing a humanreadable typedef for the result type:

You assume that such a typedef can actually be written. Automatic return type deduction means that types with very complex typenames can be returned. Not all of them can be easily predicted and culled out with a typedef.

Antony Polukhin

unread,
Dec 27, 2016, 12:43:11 AM12/27/16
to std-pr...@isocpp.org
2016-12-25 22:27 GMT+03:00 Nicol Bolas <jmck...@gmail.com>:
>
>
> On Sunday, December 25, 2016 at 2:13:57 PM UTC-5, Antony Polukhin wrote:
>>
>> 2016-11-18 19:01 GMT+03:00 Avi Kivity <a...@scylladb.com>:
>> >
>> > map<foo, map<set<bar>, baz>>
>> > complicated(...) {
>> > decltype(return) ret;
>> > // add stuff to ret
>> > return try;
>> > }
>> >
>> > Instead of repeating the complicated type, refer to it via
>> > decltype(return).
>> > Won't work with deduced return types, of course.
>>
>> Syntax sugar makes the language harder to understand.
>
>
> No, it doesn't. `for` loops are syntactic sugar for `while` loops.
> Range-based `for` loops are syntactic sugar for `for` loops over the
> begin/end iterators. And technically, you could implement all of those with
> `goto`.

All that sugar exist in many popular languages. decltype(return) does not.

> That depends on how many lines it takes to enter that return type. After
> all, you can use `decltype(return)` even in a function that deduces its
> return type from a return statement. So that return type could be quite
> lengthy.

It's already hard to understand what is happening if there's multiple
chained functions with auto return type. Adding decltype(return) may
make the things wose.

>> motivates people to write ugly return types instead of providing a
>> humanreadable typedef for the result type:
>
>
> You assume that such a typedef can actually be written. Automatic return
> type deduction means that types with very complex typenames can be returned.
> Not all of them can be easily predicted and culled out with a typedef.

Could you give an example?

Antony Polukhin

unread,
Dec 27, 2016, 12:45:15 AM12/27/16
to std-pr...@isocpp.org
How often do you have such cases? Adding a core language construction
for cases that occur once a year does not seem right to me.

Thiago Macieira

unread,
Dec 27, 2016, 6:25:00 AM12/27/16
to std-pr...@isocpp.org
Em terça-feira, 27 de dezembro de 2016, às 08:43:08 BRST, Antony Polukhin
escreveu:
> It's already hard to understand what is happening if there's multiple
> chained functions with auto return type. Adding decltype(return) may
> make the things wose.

That's not the language's fault. We shouldn't forego adding something useful
because someone might misuse it and make their bad code worse.

--
Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org
Software Architect - Intel Open Source Technology Center

Nicol Bolas

unread,
Dec 27, 2016, 10:04:45 AM12/27/16
to ISO C++ Standard - Future Proposals
On Tuesday, December 27, 2016 at 12:43:11 AM UTC-5, Antony Polukhin wrote:
2016-12-25 22:27 GMT+03:00 Nicol Bolas <jmck...@gmail.com>:
>
>
> On Sunday, December 25, 2016 at 2:13:57 PM UTC-5, Antony Polukhin wrote:
>>
>> 2016-11-18 19:01 GMT+03:00 Avi Kivity <a...@scylladb.com>:
>> >
>> > map<foo, map<set<bar>, baz>>
>> > complicated(...) {
>> >     decltype(return) ret;
>> >     // add stuff to ret
>> >     return try;
>> > }
>> >
>> > Instead of repeating the complicated type, refer to it via
>> > decltype(return).
>> > Won't work with deduced return types, of course.
>>
>> Syntax sugar makes the language harder to understand.
>
>
> No, it doesn't. `for` loops are syntactic sugar for `while` loops.
> Range-based `for` loops are syntactic sugar for `for` loops over the
> begin/end iterators. And technically, you could implement all of those with
> `goto`.

All that sugar exist in many popular languages. decltype(return) does not.

My overall point was that your statement "Syntax sugar makes the language harder to understand," is wrong.

"many popular languages" don't have typenames that are as large as C++ typenames can get. What other languages do or don't do is not by itself sufficient justification for what C++ should do or not do.

You can argue that it makes code harder to understand. I don't agree that this is sufficient to warrant not having this feature, but you can argue it. But arguing that we shouldn't have feature just because other languages don't doesn't make sense. We're not making "many popular languages"; we're making C++.

Languages are not supposed to be carbon copies of each other.

> That depends on how many lines it takes to enter that return type. After
> all, you can use `decltype(return)` even in a function that deduces its
> return type from a return statement. So that return type could be quite
> lengthy.

It's already hard to understand what is happening if there's multiple
chained functions with auto return type. Adding decltype(return) may
make the things wose.

>> motivates people to write ugly return types instead of providing a
>> humanreadable typedef for the result type:
>
>
> You assume that such a typedef can actually be written. Automatic return
> type deduction means that types with very complex typenames can be returned.
> Not all of them can be easily predicted and culled out with a typedef.

Could you give an example?

A lot of the stuff in Boost.Spirit, for example. What is the return type of a complex expression of Spirit parsers?

Avi Kivity

unread,
Dec 28, 2016, 3:38:52 AM12/28/16
to std-pr...@isocpp.org
On 12/27/2016 07:43 AM, Antony Polukhin wrote:
> 2016-12-25 22:27 GMT+03:00 Nicol Bolas <jmck...@gmail.com>:
>>
>> On Sunday, December 25, 2016 at 2:13:57 PM UTC-5, Antony Polukhin wrote:
>>> 2016-11-18 19:01 GMT+03:00 Avi Kivity <a...@scylladb.com>:
>>>> map<foo, map<set<bar>, baz>>
>>>> complicated(...) {
>>>> decltype(return) ret;
>>>> // add stuff to ret
>>>> return try;
>>>> }
>>>>
>>>> Instead of repeating the complicated type, refer to it via
>>>> decltype(return).
>>>> Won't work with deduced return types, of course.
>>> Syntax sugar makes the language harder to understand.
>>
>> No, it doesn't. `for` loops are syntactic sugar for `while` loops.
>> Range-based `for` loops are syntactic sugar for `for` loops over the
>> begin/end iterators. And technically, you could implement all of those with
>> `goto`.
> All that sugar exist in many popular languages. decltype(return) does not.
>

This sugar supports the "Don't Repeat Yourself" principle, making the
code easier to understand in some cases. Instead of parsing a long type
name twice, then understanding there is no difference, or following a
layer of indirection that was added just for the purpose, the reader
immediately sees that the type is exactly equivalent to the return
type. The syntax is intuitive so anyone who isn't a language lawyer
immediately understands what it means.

The syntax can be misused, of course, but this is C++, not Python.
Anything in C++ can be misused.

Matthew Woehlke

unread,
Dec 28, 2016, 10:32:25 AM12/28/16
to std-pr...@isocpp.org
On 2016-12-27 00:43, Antony Polukhin wrote:
> On 2016-12-25 22:27 GMT+03:00 Nicol Bolas wrote:
>> You assume that such a typedef can actually be written. Automatic return
>> type deduction means that types with very complex typenames can be returned.
>> Not all of them can be easily predicted and culled out with a typedef.
>
> Could you give an example?

template <typename A, typename B>
auto foo(A a, B b, ...) -> decltype(a * b)
{ ... }

How would you write a typedef of that? Assume that `A` and/or `B`
*cannot be default constructed*.

That particular example can probably be named in the function itself
without too much difficulty, but imagine the return type is less simple,
but still argument dependent in a similar manner...

--
Matthew

Ville Voutilainen

unread,
Dec 28, 2016, 10:42:42 AM12/28/16
to ISO C++ Standard - Future Proposals
On 28 December 2016 at 17:32, Matthew Woehlke <mwoehlk...@gmail.com> wrote:
> template <typename A, typename B>
> auto foo(A a, B b, ...) -> decltype(a * b)
> { ... }
>
> How would you write a typedef of that? Assume that `A` and/or `B`
> *cannot be default constructed*.

template <class A, class B> using ab_prod = decltype(declval<A>() *
declval<B>());

Marc

unread,
Dec 29, 2016, 1:10:24 PM12/29/16
to ISO C++ Standard - Future Proposals

This does not look equivalent to me, declval<A&> would be closer.

Ville Voutilainen

unread,
Dec 29, 2016, 1:32:22 PM12/29/16
to ISO C++ Standard - Future Proposals
Probably. The point being that whether a type is or is not
default-constructible doesn't
hurt the ability to write such a typedef.

ionto...@gmail.com

unread,
Jan 3, 2017, 6:11:58 AM1/3/17
to ISO C++ Standard - Future Proposals
I like that syntax! I was thinking of something like that for a hypothetical multiple return types feature


On Wednesday, November 23, 2016 at 1:50:46 AM UTC-8, Andrzej Krzemieński wrote:


W dniu piątek, 18 listopada 2016 17:01:41 UTC+1 użytkownik Avi Kivity napisał:

map<foo, map<set<bar>, baz>
complicated(...) {
     decltype(return) ret;
     // add stuff to ret
     return try;
}

Instead of repeating the complicated type, refer to it via
decltype(return).  Won't work with deduced return types, of course.
In your example, you use this hypothetical feature to create up front the value you will be returning. I wonder if this is the only use case that drives your feature, because if so, you could consider an alternative feature: give a variable name in the place where you declare the return type:

auto complicated(...) -> map<foo, map<set<bar>, baz> ret // ret is a "return variable"
{

 
// add stuff to ret
} // no return statement is ok: we have an implicit return variable

Of course, your solution is more general, it also covers other cases:

map<foo, map<set<bar>, baz>
complicated(...)
{
  decltype(return) a, b;
  // populate a and b
  if (is_better(a, b))
    return a;
  else
    return b;
}

Regards,
&rzej;
Reply all
Reply to author
Forward
0 new messages