[...]
I'm getting a compile error while trying to return an unnamed struct from a function (I don't know where in the standard this limitation appears)
struct { // ERROR
int first;
int second;
}
a_pair()
{
return {1, -1};
}
error: '(anonymous struct at pair_pass.cpp:91:7)' cannot be defined in the result type of a function
struct {
[...]
Is there any reason to don't support functions/lambdas returning unnamed structs?
On 2015-12-30 11:37, Vicente J. Botet Escriba wrote:
> Le 30/12/2015 15:24, Giovanni Piero Deretta a écrit :
>> This works in C++14:
>>
>> auto a_pair ()
>> {
>> struct { int first; int second; } r {0, 1};
>> return r;
>> }
>>
>> Is this similar to what you had in mind?
>
> Hmm, yes and not. This works if the function body is in the declaration.
> However, it is not practical to define an interface in the standard :(
Functions cannot be overloaded by return value. That being the case, is
there a reason why this "shouldn't" work?
// foo.h
struct { int first; double second; } foo(...);
// foo.cpp
auto foo(...)
{
...
return { 1, 5.25 };
}
By "shouldn't", I don't mean according to the current standard (or maybe
it "should" in that sense already?), but rather as a possible proposal.
More generally, would it be reasonable for a definition having a return
value of 'auto' with no trailing return specification, for which a
declaration has been previously seen that had a concrete return type, to
"inherit" the previously seen return type?
Hi,
n4560 introduce the classes tagged, tagged_pair and tagged_tuple. Here follows the rationale as described in n4560:
[Editor’s note: This type exists so that the algorithms can return pair- and tuple-like objects with named accessors, as requested by LEWG. Rather than create a bunch of one-off class types with no relation to pair and tuple, I opted instead to create a general utility. I’ll note that this functionality can be merged into pair and tuple directly with minimal breakage, but I opted for now to keep it separate.]
I was wondering if
tagged_pair<tag::in(I), tag::fun(Fun)>
couldn't be better be represented by the unnamed aggregate
struct {
I in;
Fun fun;
}
which has less noise.
I'm getting a compile error while trying to return an unnamed struct from a function (I don't know where in the standard this limitation appears)
Types shall not be defined in return or parameter types.
template<typename Iter> struct equal_range_return { Iter first; Iter last; operator pair<Iter, Iter>() { return { first, last }; }};
template<int N, typename Iter> struct get_return_helper {};template<typename Iter> struct get_return_helper<0, Iter> { Iter get(equal_range_return<Iter>& range) { return range.first; }};template<typename Iter> struct get_return_helper<1, Iter> { Iter get(equal_range_return<Iter>& range) { return range.last; }};
template<int N, typename Iter> Iter get(equal_range_return<Iter>& range) { return get_return_helper<N, Iter>::get(range);}
template<int N, typename Iter> using tuple_element_t = Iter;
template<typename Iter, typename T> equal_range_return<Iter> equal_range(Iter from, Iter to, const T& value);
The MSVC support is however very shallow, this does not work:
typedef struct { int i; } A;
typedef struct { int i; } B;
static_assert(!std::is_same<A, B>{}, "");Note however that all of this is just to rename 'second' to 'last' in std::equal_range. For std::mismatch the names first and second are actually hard to better... I didn't find any other std algorithms that return two or more values.
I think that returning by unnamed struct is the best way to return multiple values proposed so far. A pity that it is a non-standard feature (that Microsoft's compiler supports). The MSVC support is however very shallow, this does not work:struct { int x; const char* y; } foo(){return { 1, "Hej" };}struct { int y; const char* x; } fum(){return foo(); // ERROR: 'return': cannot convert from '' to ''}As suspected aligning the member names does not help either.So, the question is: How hard would it be to define that this should work -- and how.
On Wednesday, December 30, 2015 at 6:02:45 PM UTC-5, Bengt Gustafsson wrote:I think that returning by unnamed struct is the best way to return multiple values proposed so far. A pity that it is a non-standard feature (that Microsoft's compiler supports). The MSVC support is however very shallow, this does not work:struct { int x; const char* y; } foo(){return { 1, "Hej" };}struct { int y; const char* x; } fum(){return foo(); // ERROR: 'return': cannot convert from '' to ''}As suspected aligning the member names does not help either.So, the question is: How hard would it be to define that this should work -- and how.
Very hard. Well, at least directly.
Building on what T. C., a lot of C++ seems built on the idea that each type definition is distinct and that there is only one definition for each type. To rewrite those rules would be... painful.
However, since we're specifically talking about a very specific case, it might be reasonable to have a special rule that says that two anonymous structs that are layout-compatible are not the same types, but they would be "equivalent types". And two equivalent anonymous structs would be implicitly convertible from one to the other (and can also participate in elision where appropriate).
Any explicit conversions would of course take priority. Granted, explicit conversions would require extreme trickery to declare (using `decltype` and such).
I think that returning by unnamed struct is the best way to return multiple values proposed so far. A pity that it is a non-standard feature (that Microsoft's compiler supports). The MSVC support is however very shallow, this does not work:
struct { int x; const char* y; } foo(){return { 1, "Hej" };}
struct { int y; const char* x; } fum(){return foo(); // ERROR: 'return': cannot convert from '' to ''}
As suspected aligning the member names does not help either.
So, the question is: How hard would it be to define that this should work -- and how. I guess that this should be limited to POD structs and that the type of two unnamed structs should be considered compatible iff the list of types of data members are the same.
Actually, to solve the problem that the original proposer imagined the std algorithms in question could be changed to return a named struct and then the tuple access methods get<> and tuple_element<> could be overloaded for these struct types to handle backwards compatibility, complemented by a cast operator to the original tuple type. This covers most of the compatibility issues without language change. It is of course possible to construct code that reveals the standard change but these should be very rare in production code. Something like this:
template<typename Iter> struct equal_range_return {Iter first;Iter last;operator pair<Iter, Iter>() { return { first, last }; }};
template<int N, typename Iter> struct get_return_helper {};template<typename Iter> struct get_return_helper<0, Iter> {Iter get(equal_range_return<Iter>& range) { return range.first; }};template<typename Iter> struct get_return_helper<1, Iter> {Iter get(equal_range_return<Iter>& range) { return range.last; }};
template<int N, typename Iter> Iter get(equal_range_return<Iter>& range) {return get_return_helper<N, Iter>::get(range);}
template<int N, typename Iter> using tuple_element_t = Iter;
template<typename Iter, typename T> equal_range_return<Iter> equal_range(Iter from, Iter to, const T& value);
Note however that all of this is just to rename 'second' to 'last' in std::equal_range. For std::mismatch the names first and second are actually hard to better... I didn't find any other std algorithms that return two or more values.
Given these possibilities I don't think that the rather large machinery needed to introduce a second way to name members of aggregates as proposed in N4560 is warranted. The use case is rather weak from the onset, and as it can be emulated quite easily as shown with only minor backwards compatibility issues I would not support such a proposal (if I had a say).
Allowing return by anonymous struct in a more complete way than Microsoft currently supports has a much wider applicability and seems like a better proposal to put work into. It also has the advantage of removing a non-orthogonal restriction from the standard rather than adding a new feature which increases the learning curve.
Vicente
struct { int i; } a, foo();
struct { int i; } bar();
On Wednesday, December 30, 2015 at 6:02:45 PM UTC-5, Bengt Gustafsson wrote:I think that returning by unnamed struct is the best way to return multiple values proposed so far. A pity that it is a non-standard feature (that Microsoft's compiler supports). The MSVC support is however very shallow, this does not work:
struct { int x; const char* y; } foo(){return { 1, "Hej" };}
struct { int y; const char* x; } fum(){return foo(); // ERROR: 'return': cannot convert from '' to ''}
As suspected aligning the member names does not help either.
So, the question is: How hard would it be to define that this should work -- and how.
Very hard. Well, at least directly.
Building on what T. C., a lot of C++ seems built on the idea that each type definition is distinct and that there is only one definition for each type. To rewrite those rules would be... painful.
However, since we're specifically talking about a very specific case, it might be reasonable to have a special rule that says that two anonymous structs
that are layout-compatible are not the same types, but they would be "equivalent types". And two equivalent anonymous structs would be implicitly convertible from one to the other (and can also participate in elision where appropriate).
What I'm looking for and I believe we need is that
is_same<KEYWORD { int x; const char* y; }, KEYWORD { int x; const char* y; }>
[...]
That is, KEYWORD {...} would just be another way to construct new types, as we do with T*, T&, const T, volatile T, T[n].
KEYWORD {} could be limited to contain only data members. The extreme case could be that the member data are even unnamed
KEYWORD { int ; const char*; }
This should define almost the same as tuple<int, const char*>.
Conversion from two such types that have the same data member types but with different data member names would be desirable.
Conversion(promotion) from two such types that have the convertible data member types would be desirable.
If in addition std::pair<T,U> were convertible from any type KEYWORD { T _1; U _2;} (for any field names), the backward compatibility issue could be almost ensured. How to define this conversion needs more insight.
The question is then if we want to define something new at the language level that represents a named tuple that could be used in particular to return multiple values from a function.
On Thursday, December 31, 2015 at 11:45:27 AM UTC, Vicente J. Botet Escriba wrote:
What I'm looking for and I believe we need is that
is_same<KEYWORD { int x; const char* y; }, KEYWORD { int x; const char* y; }>
are you sure you need is_same to hold (i.e. nominal type equality) ...
[...]
That is, KEYWORD {...} would just be another way to construct new types, as we do with T*, T&, const T, volatile T, T[n].
KEYWORD {} could be limited to contain only data members. The extreme case could be that the member data are even unnamed
KEYWORD { int ; const char*; }
This should define almost the same as tuple<int, const char*>.
instead of just:
Conversion from two such types that have the same data member types but with different data member names would be desirable.
Conversion(promotion) from two such types that have the convertible data member types would be desirable.
i.e. some sort of opt-in structural typing.
If in addition std::pair<T,U> were convertible from any type KEYWORD { T _1; U _2;} (for any field names), the backward compatibility issue could be almost ensured. How to define this conversion needs more insight.
The question is then if we want to define something new at the language level that represents a named tuple that could be used in particular to return multiple values from a function.
I was toying with a possible language extension (a ` quote operator) to lift symbols to first class values that would allow:
auto t = std::tup(`first = 10, `second = "hello"s); // t has .first and .second members of appropriate types
struct { int first; std::string second; } s = t; // ok
std::pair<int, std::string> p = t; // also ok
A pure macro based implementation here : https://github.com/gpderetta/libtask/blob/broken/tests/q_test.cpp
It uses the $(<symbol>) syntax instead of `<symbol>, but it is otherwise complete.
The library approach uses lambdas under the hood, so you can't put a $(<symbol>) expression directly inside decltype for now.
Hi,
n4560 introduce the classes tagged, tagged_pair and tagged_tuple. Here follows the rationale as described in n4560:
[Editor’s note: This type exists so that the algorithms can return pair- and tuple-like objects with named accessors, as requested by LEWG. Rather than create a bunch of one-off class types with no relation to pair and tuple, I opted instead to create a general utility. I’ll note that this functionality can be merged into pair and tuple directly with minimal breakage, but I opted for now to keep it separate.]
I was wondering if
tagged_pair<tag::in(I), tag::fun(Fun)>
couldn't be better be represented by the unnamed aggregate
struct {
I in;
Fun fun;
}
which has less noise.
struct {int x; float y} func() {...}struct MeaningfulName {int x; float y};
MeaningfulName func() {...}struct {int x; float y} other_func() {...}
struct {int x; float y} func() {return other_func();}On Wednesday, December 30, 2015 at 8:20:35 AM UTC-5, Vicente J. Botet Escriba wrote:Hi,
n4560 introduce the classes tagged, tagged_pair and tagged_tuple. Here follows the rationale as described in n4560:
[Editor’s note: This type exists so that the algorithms can return pair- and tuple-like objects with named accessors, as requested by LEWG. Rather than create a bunch of one-off class types with no relation to pair and tuple, I opted instead to create a general utility. I’ll note that this functionality can be merged into pair and tuple directly with minimal breakage, but I opted for now to keep it separate.]
I was wondering if
tagged_pair<tag::in(I), tag::fun(Fun)>
couldn't be better be represented by the unnamed aggregate
struct {
I in;
Fun fun;
}
which has less noise.
Here's a question.
From my perspective, the principle reason to use a tuple is metaprogramming. That is, you're assembling an aggregation of values from some some user-provided construct. A tagged tuple has limited metaprogramming value, but it still has some value in that regard. By tagging types with names, you can develop interfaces between the sender and the receiver of a tuple that allow you to more effectively communicate. Rather than communicating by type or index, you communicate by a name, thus giving the sending code the freedom to pick and choose where the element is in the tuple.
That's the reason why we need tagged tuples, and the above case won't even touch that one. Reflection-based aggregate creation might fix it, but that's for C++20, if even that.
Using a tuple for plain-old multiple-return-values has always been, to me, of somewhat dubious merit. It's not a terrible, and it's a solution that'll be far more acceptable with structured binding. But using a tagged tuple as a return value from a concrete function? No; that makes no sense.
Clearly you should be returning a struct. But that's where I don't get the point of this proposal. Because, well, why does it have to be "unnamed"? I mean, are names that precious that we need to have specialized syntax to do this? Is this:
struct {int x; float y} func() {...}
Really that much better than this?
struct MeaningfulName {int x; float y};
MeaningfulName func() {...}
If the type returned by this function needs to name the individual multiple values, then that aggregation of values probably has some meaning. 9 times out of 10, other functions will want to return that same type. So you're going to need a named type.
Just consider the main confounding problem of this proposal:
struct {int x; float y} other_func() {...}
struct {int x; float y} func() {return other_func();}
Even if we found a way to make this work, I don't really want to see it. When I look at this code, I see repetition; I see the same type being written twice. I see someone who has not created a named type who clearly ought to.
After all, what is the point of assigning names to the members of the return value? It's so that the receiver of the object knows what those values mean. So that we don't have the `map::insert` idiocy where "ret.second" is what tells you whether the object was correctly inserted.
If so... you need a struct. A named struct. `std::map` proves that, since it has several interface functions that all return the same `pair<iterator, bool>`, and all of them have the same meaning (the bool tells whether insertion happened, the iterator says where the element is). If two types have the same functionality and meaning, they are the same type.
And in C++, we declare two types to be the same by giving them the same name.
So it seems to me that unnamed struct return values would only be useful in cases where:
1) `tuple` is inappropriate. This would suggest that the semantic meaning of each independent return value is both significant and not obvious from just the type and function name (`pair` returned from `map::insert` is a good example). And `tagged_tuple`, well, sucks at this for obvious reasons.
2) That function is the only function that returns this type or does anything with it in any way. If the function has even one other overload, then you ought to use a named type.
How often do you encounter both #1 and #2? Is this often enough to justify such a language feature? With structured binding making tuples far more usable for MRV than they have ever been, I just can't understand why we need unnamed struct returns like this.
{int x; float y} other_func(int a, int b) {...}
auto other_func(int a, int b) -> {int x; float y} {...}
Le 03/01/2016 04:07, Nicol Bolas a écrit :
On Wednesday, December 30, 2015 at 8:20:35 AM UTC-5, Vicente J. Botet Escriba wrote:Here is the problem of naming the result type. When we talk of returning several values is because we don't have yet one type to aggregate them. Most of the time a name is is not meaningful.Hi,
n4560 introduce the classes tagged, tagged_pair and tagged_tuple. Here follows the rationale as described in n4560:
[Editor’s note: This type exists so that the algorithms can return pair- and tuple-like objects with named accessors, as requested by LEWG. Rather than create a bunch of one-off class types with no relation to pair and tuple, I opted instead to create a general utility. I’ll note that this functionality can be merged into pair and tuple directly with minimal breakage, but I opted for now to keep it separate.]
I was wondering if
tagged_pair<tag::in(I), tag::fun(Fun)>
couldn't be better be represented by the unnamed aggregate
struct {
I in;
Fun fun;
}
which has less noise.
Here's a question.
From my perspective, the principle reason to use a tuple is metaprogramming. That is, you're assembling an aggregation of values from some some user-provided construct. A tagged tuple has limited metaprogramming value, but it still has some value in that regard. By tagging types with names, you can develop interfaces between the sender and the receiver of a tuple that allow you to more effectively communicate. Rather than communicating by type or index, you communicate by a name, thus giving the sending code the freedom to pick and choose where the element is in the tuple.
That's the reason why we need tagged tuples, and the above case won't even touch that one. Reflection-based aggregate creation might fix it, but that's for C++20, if even that.
Using a tuple for plain-old multiple-return-values has always been, to me, of somewhat dubious merit. It's not a terrible, and it's a solution that'll be far more acceptable with structured binding. But using a tagged tuple as a return value from a concrete function? No; that makes no sense.
Clearly you should be returning a struct. But that's where I don't get the point of this proposal. Because, well, why does it have to be "unnamed"? I mean, are names that precious that we need to have specialized syntax to do this? Is this:
struct {int x; float y} func() {...}
Really that much better than this?
struct MeaningfulName {int x; float y};
MeaningfulName func() {...}
If the type returned by this function needs to name the individual multiple values, then that aggregation of values probably has some meaning. 9 times out of 10, other functions will want to return that same type. So you're going to need a named type.
enum class did_insert : unsigned char
{
no_insert = 0,
inserted,
};
pair<iterator, did_insert> insert(...);
using pos_offset = ... //Weak alias of `size_t`.
pair<int, pos_offset> stoi(string_span str, int base = 10);Think of it as if we had a single parameter with all the input values of a function call. Giving a name would be most of the time not meaningful. We just have instead several parameters.
Just consider the main confounding problem of this proposal:Do you prefer to name it ResultType_for_func_and_other_func?
struct {int x; float y} other_func() {...}
struct {int x; float y} func() {return other_func();}
Even if we found a way to make this work, I don't really want to see it. When I look at this code, I see repetition; I see the same type being written twice. I see someone who has not created a named type who clearly ought to.
After all, what is the point of assigning names to the members of the return value? It's so that the receiver of the object knows what those values mean. So that we don't have the `map::insert` idiocy where "ret.second" is what tells you whether the object was correctly inserted.unnamed struct give exactly that.
If so... you need a struct. A named struct. `std::map` proves that, since it has several interface functions that all return the same `pair<iterator, bool>`, and all of them have the same meaning (the bool tells whether insertion happened, the iterator says where the element is). If two types have the same functionality and meaning, they are the same type.Waiting for your proposal to change this ;-)
How often do you encounter both #1 and #2? Is this often enough to justify such a language feature? With structured binding making tuples far more usable for MRV than they have ever been, I just can't understand why we need unnamed struct returns like this.Take in account that a lot of the current code is written using references as output parameters. Things are changing since move semantics, RTO, ...
I will replay to you with another questions.
How many times do you find 1) is useful?
I have not found a single one at the user level.
How many times do you have a function that has more than one output parameters (result)?
Much more than we used to have.
So, if the question you are raising is if it is worth doing something so that we can solve this multiple-value return,
I believe the the answer of the standard committee is yes, otherwise we will not have tagged tuples, structural bindings, ...
P.S. Note that I'm not against giving a name to an aggregation when the aggregation has more meaning than just its parts.
`map::insert` is a good example, because `pair<iterator, bool>` is really impenetrable as to its meaning. The `iterator`, you can figure out what that probably means. But the `bool` is completely meaningless; you have to look up the documentation to know what it means.
So in a better designed system, this would be a struct with two members. You say that the name of such a struct "is not meaningful". I contest this. A decent name for such a struct would be "insertion_state". That's what it is and that's what it is used for: telling you the state of the system after insertion. That name is meaningful.
I don't think whether the name is meaningful is a good litmus test for your case. I think a better one is this: what is the likelihood that a user will want to keep that aggregate around as an aggregate. That is, would a user be willing to pass it around to other functions? Not just as pass-through return values, but as a parameter to some other function. Will the user store it in some type for longer-duration keeping?
It is in this case where `insertion_state` has dubious merit. Because... well, I can't see a real reason why I would write a function that takes it as a parameter. The two fields basically have nothing to do with one another.
Nor could I see a reason to store one in a larger data structure. The `iterator`, I could understand keeping around. But the `bool` has value only to the immediate caller of the insertion operation. The farther you are from that operation, the less likely you will care whether it actually inserted the object or not.
So what we're talking about is an aggregation who's nature is ephemeral, of the moment. It's not that the aggregation's name is meaningless; it's that the aggregation itself is meaningless.
That the aggregation exists solely as a workaround to the lack of MRV support in the language.
But then, that comes back to my two points. For this to matter, you have to have a meaningless aggregation and types which don't explain what they mean. After all, the only reason you want to name the return value at all is because the `bool` argument is unclear as to its meaning. If it were instead:
enum class did_insert : unsigned char
{
no_insert = 0,
inserted,
};
pair<iterator, did_insert> insert(...);
Then the tuple/pair would be perfectly fine. Both values make it clear what they mean simply from their types. So there is no need to name them.
So, here's what I would like to see. Give me a case where the aggregation is clearly ephemeral, yet the meaning of the individual values cannot be inferred from the nature of the function and the typenames of those values.
`map::insert` is problematic only because it use `bool`; if it had used a more expressive typename, it would have been fine.
I imagine such cases would be limited to things like a version stoi that returned multiple values instead of an optional output parameter. The aggregation is clearly ephemeral, but the return values are just basic integer types, without obvious meaning. It seems to me that this problem primarily occurs when the return types are basic types, which have no inherent meaning provided by the function.
This is a place where weak aliases (a distinct type from the original, but implicitly convertible to/from) could be of value. So stoi could look like:
using pos_offset = ... //Weak alias of `size_t`.
pair<int, pos_offset> stoi(string_span str, int base = 10);
This gives us clear semantic meaning. The second value is a position offset, so the first value, by process of elimination, must be the converted integer.
So it seems to me that in these cases, better aliasing support could get allow us to get past these issues. We wouldn't need named return values so much if the types had more meaning.
Think of it as if we had a single parameter with all the input values of a function call. Giving a name would be most of the time not meaningful. We just have instead several parameters.
And if multiple return values were anywhere near as common as multiple parameters, your argument might have some weight.
Just consider the main confounding problem of this proposal:Do you prefer to name it ResultType_for_func_and_other_func?
struct {int x; float y} other_func() {...}
struct {int x; float y} func() {return other_func();}
Even if we found a way to make this work, I don't really want to see it. When I look at this code, I see repetition; I see the same type being written twice. I see someone who has not created a named type who clearly ought to.
Obvious strawman arguments aren't helping your case.After all, what is the point of assigning names to the members of the return value? It's so that the receiver of the object knows what those values mean. So that we don't have the `map::insert` idiocy where "ret.second" is what tells you whether the object was correctly inserted.unnamed struct give exactly that.
So would a named struct, which requires zero language changes.
If so... you need a struct. A named struct. `std::map` proves that, since it has several interface functions that all return the same `pair<iterator, bool>`, and all of them have the same meaning (the bool tells whether insertion happened, the iterator says where the element is). If two types have the same functionality and meaning, they are the same type.Waiting for your proposal to change this ;-)
To change what?
How often do you encounter both #1 and #2? Is this often enough to justify such a language feature? With structured binding making tuples far more usable for MRV than they have ever been, I just can't understand why we need unnamed struct returns like this.Take in account that a lot of the current code is written using references as output parameters. Things are changing since move semantics, RTO, ...
... I don't really know what you're getting at with this statement.
I will replay to you with another questions.
How many times do you find 1) is useful?
I have not found a single one at the user level.
How many times do you have a function that has more than one output parameters (result)?
Much more than we used to have.
For me? Not very often. Indeed, almost never. Maybe my C++ experience causes me to subconsciously design around the limitations of single return values, but I have rarely written functions where I have felt a genuine need to return multiple things. Perhaps if I had a functional programming background, I'd use them more frequently. But for me, MRV is not particularly common.
My experience with Lua, which has native (and very cool) support for multiple return values, bears this out. In that language, I have used MRVs more frequently than tuple return values in C++. But at the same time, I have pretty much never wanted to name individual return values. In virtually every case, what the multiple values meant was readily apparent; there was no need to give each one a semantic name.
And if there was a need, then I returned a Lua table with named fields. Now, this is where the analogy breaks down, because Lua doesn't have static types. But I wouldn't have had any problem giving those aggregations of values a name.
You say that you use MRVs more frequently than you used to. Why? What is your code doing that needs to throw around multiple values like this? It is not clear to me what has changed that causes you to code like this.
So, if the question you are raising is if it is worth doing something so that we can solve this multiple-value return,
I believe the the answer of the standard committee is yes, otherwise we will not have tagged tuples, structural bindings, ...
Structured bindings and tagged tuples are two completely different things.
Structured binding doesn't even have to do with naming return values, since it's the receiver who is giving it a meaningful name.
A name that notably could be wrong.
Indeed, if you call a function that returns named MRVs, you shouldn't use structured bindings on the return value (most of the time).
You'd just use the aggregation directly, accessing the member fields using the convenient names.
P.S. Note that I'm not against giving a name to an aggregation when the aggregation has more meaning than just its parts.
As a general courtesy to my users, if I have to return a `pair` or `tuple` for MRVs, I wrap it in a typedef, so they don't have to type the whole thing themselves. I give names to meaningless, ephemeral aggregations all the time.
I could buy this one. But I would prefer status_and_value<bool, Iterator>. We will need to discus it and find a consensus. You see, naming takes time. I believe this time must be spent when the type has interest on its own, not just because there is a function that returns these two values.So in a better designed system, this would be a struct with two members. You say that the name of such a struct "is not meaningful". I contest this. A decent name for such a struct would be "insertion_state". That's what it is and that's what it is used for: telling you the state of the system after insertion. That name is meaningful.
Ugh, and access with first and second :( Or are you suggesting the use of get<Iterator>(res) and get<did_insert>(res)?But then, that comes back to my two points. For this to matter, you have to have a meaningless aggregation and types which don't explain what they mean. After all, the only reason you want to name the return value at all is because the `bool` argument is unclear as to its meaning. If it were instead:
enum class did_insert : unsigned char
{
no_insert = 0,
inserted,
};
pair<iterator, did_insert> insert(...);
Then the tuple/pair would be perfectly fine. Both values make it clear what they mean simply from their types. So there is no need to name them.
A function returning twice the same type ?So, here's what I would like to see. Give me a case where the aggregation is clearly ephemeral, yet the meaning of the individual values cannot be inferred from the nature of the function and the typenames of those values.
Independently of the language change this could mean. Would you use unnamedstruct if they were on the language or would you create a specific named struct?
A change on the standard library, of course.If so... you need a struct. A named struct. `std::map` proves that, since it has several interface functions that all return the same `pair<iterator, bool>`, and all of them have the same meaning (the bool tells whether insertion happened, the iterator says where the element is). If two types have the same functionality and meaning, they are the same type.Waiting for your proposal to change this ;-)
To change what?
I want to signal that I would expect that there will be more a more MRV functions in the future, and that it is worth providing a simple and clear interface for them.How often do you encounter both #1 and #2? Is this often enough to justify such a language feature? With structured binding making tuples far more usable for MRV than they have ever been, I just can't understand why we need unnamed struct returns like this.Take in account that a lot of the current code is written using references as output parameters. Things are changing since move semantics, RTO, ...
... I don't really know what you're getting at with this statement.
How many time do you then return an aggregate by value?I will replay to you with another questions.
How many times do you find 1) is useful?
I have not found a single one at the user level.
How many times do you have a function that has more than one output parameters (result)?
Much more than we used to have.
For me? Not very often. Indeed, almost never. Maybe my C++ experience causes me to subconsciously design around the limitations of single return values, but I have rarely written functions where I have felt a genuine need to return multiple things. Perhaps if I had a functional programming background, I'd use them more frequently. But for me, MRV is not particularly common.
Move semantic, RVO.My experience with Lua, which has native (and very cool) support for multiple return values, bears this out. In that language, I have used MRVs more frequently than tuple return values in C++. But at the same time, I have pretty much never wanted to name individual return values. In virtually every case, what the multiple values meant was readily apparent; there was no need to give each one a semantic name.
And if there was a need, then I returned a Lua table with named fields. Now, this is where the analogy breaks down, because Lua doesn't have static types. But I wouldn't have had any problem giving those aggregations of values a name.
You say that you use MRVs more frequently than you used to. Why? What is your code doing that needs to throw around multiple values like this? It is not clear to me what has changed that causes you to code like this.
We are on a C++ standard proposals ML. What each one should do or not do is not important here.Indeed, if you call a function that returns named MRVs, you shouldn't use structured bindings on the return value (most of the time).
Nor is it expected that
the work to add this "feature" to compilers will be difficult.
Many
already support it to varying degrees, and some may be in the same boat
as the standard, that removing the restriction actually makes them
*less* complicated.
Anyway, as Vicente noted, which is easier to write and understand?:
auto const result = map.insert(...);
// Option 1
if (result.second) { ... }
// Option 2
if (get<std::did_insert>(result)) { ... }
// Option 3
if (result.inserted) { ... }
Option 1 required us to look up the return type to know what was
happening (hopefully our IDE helped). Option 2 is verbose and peculiar.
Option 3 is simple and immediately clear.
auto {it, inserted} = map.insert();
if(inserted) {...}The argument that your way requires more reading is also dubious.
Compare the code you gave above (six lines!) to:
struct { iterator it; bool inserted; } insert(...);
> On Sunday, January 3, 2016 at 5:35:33 AM UTC-5, Vicente J. Botet Escriba
> wrote:
>> How many times do you have a function that has more than one output
>> parameters (result)?
Any time a non-primitive-type is returned :-).
>> Much more than we used to have.
>
> For me? Not very often. Indeed, almost never.
Almost any function with output parameters - and you *know* those exist
- would be better written with MRV's. Things like std::expected¹ should
help, but there are going to continue to be corner cases where no
"canned" complex type is appropriate.
> Maybe my C++ experience causes me to subconsciously design *around*
> the limitations of single return values
I would say that's definitely true (based, if nothing else, on an
impression that I do so myself). It would be better to consider their
prevalence in languages such as Python or JS where MRV's (anonymous
objects, in the JS case) are usable without the sorts of jumping through
hoops that current C++ requires to use them.
Related, I would guess there are quite some times that the lack of easy
to use MRV's results in people simply doing without (and producing an
inferior API as a result). This is another key distinction I think needs
to be made... it's not so much that this feature is *required*, as that
it would be *beneficial*. After all, range-based for is not *required*
(we got by many years without it, and in C++11 it can be implemented as
a macro), but it's obviously *beneficial*.
> In virtually every case, what the multiple values meant was readily
> apparent; there was no need to give each one a semantic name.
This is definitely *not* my experience in Python. And, frankly, I'd
probably tend to use anonymous structs for MRV's (if available) over
tuples, just on principle (of them being more self-documenting).
auto x = foo();
tuple<int, float, std::string> x = foo();pair<iterator, did_insert> x = mapy.insert(...);
auto {it, did_insert} = mapy.insert(...);Typename x = foo();
> And if there was a need, then I returned a Lua table with named fields.
How is that unlike returning an anonymous struct? ;-)
On 2016-01-02 22:07, Nicol Bolas wrote:
> [...]
> Clearly you should be returning a struct. But that's where I don't get the
> point of this proposal. Because, well, why does it *have to be* "unnamed"?
> I mean, are names *that precious*
Yes. Imagine you have five methods in a class that have MRV's. What do
you name them?
Naming things is *hard*. Naming multiple return values¹ is *especially*
hard, and bloats the API unnecessarily.
The more often MRV's are used, the worse the problem becomes.
See also Vicente's response to this point. Remember, we're talking about
the cost of naming *one-off* structures.
On the flip side, I will continue to maintain that the prohibition is
unnecessary in the first place. If we were adding complexity to the
language, I would be inclined to give greater weight to your arguments.
But we're not. We're *removing* a restriction that no longer makes
sense. The net change to the standard is a *removal*... of a whole two
words, at that. Moreover, that restriction is not even uniformly
implemented in current compilers.
Note also that *allowing* anonymous struct returns is different from
*requiring* them; folks that think they should be named will still be
able to name them :-).
> So it seems to me that unnamed struct return values would only be useful in
> cases where:
>
> 1) `tuple` is inappropriate. This would suggest that the semantic meaning
> of each independent return value is both significant and not obvious from
> just the type and function name (`pair` returned from `map::insert` is a
> good example). And `tagged_tuple`, well, sucks at this for obvious reasons.
Sure.
> 2) That function is the *only function* that returns this type or does
> anything with it in any way. If the function has even one other overload,
> then you ought to use a named type.
Fair enough.
> How often do you encounter both #1 and #2? Is this often enough to justify
> such a language feature?
Maybe not on its own. However, these are also not the only reasons. Some
have even suggested that IRTVPDUA especially has its major justification
*apart from* being able to return anonymous structs. Personally, I am
somewhat indifferent on that point; I consider that IRTVPDUA is just a
useful and obvious feature, without being strongly opinionated on
whether anonymous structs or other things are the most important use case.
> With structured binding making tuples far more usable for MRV than
> they have ever been, I just can't understand why we need unnamed
> struct returns like this.
Structured binding, if anything, makes this feature (returning anonymous
structs) *more* useful. You already gave the main reason, in your (1) above.