n4560: tagged_tuple and unnamed structs

266 views
Skip to first unread message

Vicente J. Botet Escriba

unread,
Dec 30, 2015, 8:20:35 AM12/30/15
to isocpp Ranges Study Group, std-pr...@isocpp.org
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.

There is a syntactic issue however. While it is correct to declare a variable of an unnamed struct as in

    struct {
        int first;
        int second;
    } p {1, 2};
    assert(p.first == 1);
    assert(p.second == 2);

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 {


Note that we can use an alias to fix the compile error

     using Named =
     struct {
         int first;
         int second;
     };

     Named
     a_pair()
     {
        return {1, -1};  // OK
     }

As stated in http://stackoverflow.com/questions/8026579/can-anonymous-class-be-used-as-return-types-in-c, returning unnamed structs worked in gcc-4.5.2.

This will be close to having function returning multiple values.
Is there any reason to don't support functions/lambdas returning unnamed structs?

If the standard allowed them, do you think that they could replace the proposed tagged_ classes in the Range TS?

Vicente

Giovanni Piero Deretta

unread,
Dec 30, 2015, 9:24:57 AM12/30/15
to ISO C++ Standard - Future Proposals, ran...@open-std.org
On Wednesday, December 30, 2015 at 1:20:35 PM UTC, Vicente J. Botet Escriba wrote:
[...]

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?

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?

-- gpd
 

Vicente J. Botet Escriba

unread,
Dec 30, 2015, 11:37:39 AM12/30/15
to isocpp Ranges Study Group, ISO C++ Standard - Future Proposals
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 :(

Vicente

Matthew Woehlke

unread,
Dec 30, 2015, 12:04:14 PM12/30/15
to std-pr...@isocpp.org
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?

(And, yes, in theory right now one can use decltype with a "fake" call
to the function, i.e. 'decltype(foo(...))', but this requires supplying
arguments, which is verbose and can be awkward.)

--
Matthew

Nicol Bolas

unread,
Dec 30, 2015, 1:18:10 PM12/30/15
to ISO C++ Standard - Future Proposals, mwoehlk...@gmail.com


On Wednesday, December 30, 2015 at 12:04:14 PM UTC-5, Matthew Woehlke wrote:
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?

... Of course it should. That's like the best idea anyone has had all year. Including mine.

Casey Carter

unread,
Dec 30, 2015, 2:18:12 PM12/30/15
to isocpp Ranges Study Group, std-pr...@isocpp.org
On Wed, Dec 30, 2015 at 7:20 AM, Vicente J. Botet Escriba <vicent...@wanadoo.fr> 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.

Much of the motivation for tagged is to maintain a reasonable level of backwards compatibility for algorithms that return pairs/tuples in C++14. Since tagged_foo publicly inherits from foo, a tagged_pair is-a std::pair. C++14 code that wants to e.g. use mismatch(...).first or assign the result of mismatch to a std::pair of iterators continues to be valid under the Ranges TS. That would not be the case if mismatch returns a type that doesn't have members named first and second or is not convertible to std::pair.

T. C.

unread,
Dec 30, 2015, 2:20:59 PM12/30/15
to ISO C++ Standard - Future Proposals, ran...@open-std.org


On Wednesday, December 30, 2015 at 8:20:35 AM UTC-5, Vicente J. Botet Escriba wrote:

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)


[dcl.fct]/11:

Types shall not be defined in return or parameter types.

 

Matthew Woehlke

unread,
Dec 30, 2015, 2:39:01 PM12/30/15
to std-pr...@isocpp.org
Bah. What's the reason for this restriction (which obviously isn't
enforced by all compilers)?

--
Matthew

Nicol Bolas

unread,
Dec 30, 2015, 2:52:50 PM12/30/15
to ISO C++ Standard - Future Proposals, mwoehlk...@gmail.com

Well, to be honest, until C++11 there was basically no way to use such a return value. Well, you could pass them to a function that uses template argument deduction. But you could never locally declare such a variable. So it probably wasn't considered useful.

Nobody probably considered that `auto` made such anonymous structs more viable.

Ville Voutilainen

unread,
Dec 30, 2015, 3:01:32 PM12/30/15
to ISO C++ Standard - Future Proposals
If auto isn't used, we should remember that functions can have
multiple declarations,
so latter declarations should be grokked to have the same
defined-in-return-type return
type. For members of class templates, and with any number of aliases between the
class template definition and the outside-class-template-definition
definition of a member of
a class template, the equivalence of return types gets fairly complex
fairly quickly.

Vicente J. Botet Escriba

unread,
Dec 30, 2015, 3:02:05 PM12/30/15
to std-pr...@isocpp.org, isocpp Ranges Study Group
Thanks for the describing more clearly the motivation.

It is too bad we need to be as backward compatible as possible.

Vicente

Vicente J. Botet Escriba

unread,
Dec 30, 2015, 3:04:14 PM12/30/15
to std-pr...@isocpp.org, ran...@open-std.org
Thaks for the pointer.

Vicente

Vicente J. Botet Escriba

unread,
Dec 30, 2015, 3:15:18 PM12/30/15
to std-pr...@isocpp.org, mwoehlk...@gmail.com
I find it interesting also. This feature is independent of returning unnamed structs and merits a separated proposal.

Vicente

Matthew Woehlke

unread,
Dec 30, 2015, 3:41:09 PM12/30/15
to std-pr...@isocpp.org
On 2015-12-30 15:01, Ville Voutilainen wrote:
> On 30 December 2015 at 21:52, Nicol Bolas wrote:
>> On Wednesday, December 30, 2015 at 2:39:01 PM UTC-5, Matthew Woehlke wrote:
>>> On 2015-12-30 14:20, T. C. wrote:
>>>> On Wednesday, December 30, 2015 at 8:20:35 AM UTC-5, Vicente J. Botet
>>>> Escriba wrote:
>>>> [dcl.fct]/11:
>>>>
>>>> Types shall not be defined in return or parameter types.
>>>
>>> Bah. What's the reason for this restriction (which obviously isn't
>>> enforced by all compilers)?
>>
>> Well, to be honest, until C++11 there was basically no way to use
>> such a return value. [...] Nobody probably considered that `auto`
>> made such anonymous structs more viable.

:-)

> If auto isn't used, we should remember that functions can have
> multiple declarations, so latter declarations should be grokked to
> have the same defined-in-return-type return type. For members of
> class templates, and with any number of aliases between the class
> template definition and the outside-class-template-definition
> definition of a member of a class template, the equivalence of return
> types gets fairly complex fairly quickly.

Agreed; IRTVPDUA¹ by itself may be useful, but relaxing [dcl.fct]/11
without IRTVPDUA is likely to be obnoxious. Certainly I don't intend to
propose that the standard enforce that two anonymous structs having the
same layout and being used as return types for the same (not considering
return type) function signature are the same type. I won't gripe if
compilers want to do that², but I don't think it should be required.

(¹ Inferred Return Type Via Previous Declaration, Using Auto)

(² FWIW, I don't think this is as hard as it sounds; compilers ought to
be able to easily enough compare the canonical function signatures.)

--
Matthew

Bengt Gustafsson

unread,
Dec 30, 2015, 6:02:45 PM12/30/15
to ISO C++ Standard - Future Proposals, mwoehlk...@gmail.com
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.

T. C.

unread,
Dec 30, 2015, 6:24:24 PM12/30/15
to ISO C++ Standard - Future Proposals, mwoehlk...@gmail.com

On Wednesday, December 30, 2015 at 6:02:45 PM UTC-5, Bengt Gustafsson wrote:
The MSVC support is however very shallow, this does not work:

Distinct unnamed struct definitions create distinct types. This assertion doesn't fire:

typedef struct { int i; } A;
typedef struct { int i; } B;
static_assert(!std::is_same<A, B>{}, "");

If they were the same type, you'd violate [basic.def.odr]/1: "No translation unit shall contain more than one definition of any [...] class type [...]."

Creating a special rule for just function return types seems questionable at best.
 
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.

minmax. minmax_element. partition_copy.

Nicol Bolas

unread,
Dec 30, 2015, 8:52:00 PM12/30/15
to ISO C++ Standard - Future Proposals, mwoehlk...@gmail.com
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).


 

Nicol Bolas

unread,
Dec 30, 2015, 9:40:26 PM12/30/15
to ISO C++ Standard - Future Proposals, mwoehlk...@gmail.com
On Wednesday, December 30, 2015 at 8:52:00 PM UTC-5, Nicol Bolas wrote:
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).

Note that the layout compatibility rule implies that the two types are standard layout. So types which are not standard layout wouldn't be able to get this implicit conversion.

Vicente J. Botet Escriba

unread,
Dec 31, 2015, 1:17:02 AM12/31/15
to std-pr...@isocpp.org, mwoehlk...@gmail.com
Le 31/12/2015 00:02, Bengt Gustafsson a écrit :
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.
Agreed.

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).

I admit I missed the backward compatibility requirements of the tagged_ classes and I share your concern.

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.

Any contribution in this direction is welcome.

Vicente

Giovanni Piero Deretta

unread,
Dec 31, 2015, 2:52:51 AM12/31/15
to ISO C++ Standard - Future Proposals, ran...@open-std.org

hum, what's the problem exactly? As far as I understand, the use case is range algorithms, i.e. template functions which are in practice always defined inline.  The standard spec would simply say that these algorithms return an unspecified type which has certain properties.

-- gpd
 
Vicente

Vicente J. Botet Escriba

unread,
Dec 31, 2015, 6:44:58 AM12/31/15
to isocpp Ranges Study Group, ISO C++ Standard - Future Proposals
You are right for this specific use case. However I suspect that the wording sating that the user just can access the parts and can do nothing with the global result will be not enough.

As Tony as signaled, unnamed structs are different types on the same translation unit.

Given

struct { int i; } a, foo();
struct { int i; } bar();

can you write `a = bar();`?

Something must be changed to consider unnamed structs (or something else) to have the same type if the parts are the same.
If this issue is solved and if IRTVPDUA proposal was adopted, the interface could even use the unnamed struct.

Vicente

Vicente J. Botet Escriba

unread,
Dec 31, 2015, 6:45:27 AM12/31/15
to std-pr...@isocpp.org, mwoehlk...@gmail.com
Le 31/12/2015 02:51, Nicol Bolas a écrit :
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
unnamed structs :) anonymous structs is something different.

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).

I believe that introducing conversion would make things more complex that necessary, IMO we need that we have the same type.

Next follows an alternative: I don't know yet how, but in the same way we consider that these two functions return the same type

tagged_pair<tag::x(int), tag::y(const char*> foo()
{
return { 1, "Hej" };
}

tagged_pair<tag::x(int), tag::y(const char*)> fum()
{
return foo();     // OK
}

even if we have two instantiations of the template tagged_pair and that the resulting type is the same, I would like to be able to use something as simple as

    KEYWORD { int x; const char* y; }

to mean the instantiation of a type that contains an int named x and a const char* named y.

My first trial has been with unnamed structs (KEYWORD==struct), but currently this implies the definition of two distinct types :(

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; }>

in the same way

    is_same<tagged_pair<tag::x(int), tag::y(const char*)>, tagged_pair<tag::x(int), tag::y(const char*)>>
    is_same<char*, char*>
    is_same<int[3], int[3]>
    is_same<int, int>

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.

Bike-shading for KEYWORD:

* auto?

    auto { int x; const char* y; }

    => overuse of auto

* decltype?

    decltype { int x; const char* y; }

* struct?


    struct { int x; const char* y; }

    => would need some changes on the meaning of an unnamed struct :(

* nothing?


    { int x; const char* y; }

    => IMHO this seems the more elegant.

Vicente

Giovanni Piero Deretta

unread,
Dec 31, 2015, 7:28:35 AM12/31/15
to ISO C++ Standard - Future Proposals, mwoehlk...@gmail.com
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.

Matthew Woehlke

unread,
Dec 31, 2015, 9:44:32 AM12/31/15
to std-pr...@isocpp.org
On 2015-12-30 18:02, 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 ''
> }

Well... yeah. I'm not convinced this *should* work, however.
(Presumably, though, you could use ARTIFRS¹ here, though. But only if
fum() doesn't need a separate declaration.)

(¹ Automatic Return Type Inferred From Return Statement)

Anyway, with the addition of P0144 / P0151, you could make it work like:

struct { int y; char const* x } fum()
{
auto <x, y> = foo();
return { x, y };
}

...and maybe some day you'll even be able to write:

struct { int y; char const* x } fum()
{
return { [*]foo() };
}

> 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.

struct /*polar*/ { double r; double t; };
struct /*cartesian*/ { double x; double y; };

Should those be equivalent? I think not. Again, I'm not convinced that
equivalence is a good thing. Sure, it makes some things easier, but it
seems *very* open to abuse. I'm not convinced there aren't better ways
(like tuple expansion) to achieve the same things.

--
Matthew

Matthew Woehlke

unread,
Dec 31, 2015, 9:53:07 AM12/31/15
to std-pr...@isocpp.org
On 2015-12-31 06:45, Vicente J. Botet Escriba wrote:
> I believe that introducing conversion would make things more complex
> that necessary, IMO we need that we have the same type.
> [...]
>
> 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; }>
>
> 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*>.

Uh... yeah. Only, why "almost"?

The whole justification for using anonymous structs *instead of* tuple
is that the values are *named*. If they're not named, you've defeated
the purpose. In that case, why not just use tuple?

> Conversion from two such types that have the same data member types but
> with different data member names would be desirable.

Uh... no. No it isn't. (Not implicitly, at least! See my other post for
an example why.)

And I'd still prefer to see this, for doing so explicitly:

struct { int a; double b; } x;
struct { int c; double d; } y;
x = ...;
y = { [*]x };

> 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.

Now *that* would be useful. (Hmm... actually, given reflection,
shouldn't this be possible with the addition of an appropriate template
ctor to std::pair? Naturally, std::tuple should get the same treatment.)

...or we could just hand-wave the details and leave it to compilers to
figure out (which might anyway be more efficient).

--
Matthew

Matthew Woehlke

unread,
Dec 31, 2015, 11:13:19 AM12/31/15
to std-pr...@isocpp.org
On 2015-12-30 14:20, T. C. wrote:
> [dcl.fct]/11:
>
> Types shall not be defined in return or parameter types.

In N3242 / N3690 / N3797, that's /9, not /11... Am I looking in the
wrong place? Or is /11 from an earlier version?

--
Matthew

Ville Voutilainen

unread,
Dec 31, 2015, 11:21:40 AM12/31/15
to ISO C++ Standard - Future Proposals
Perhaps n4567?

Matthew Woehlke

unread,
Dec 31, 2015, 11:41:31 AM12/31/15
to std-pr...@isocpp.org
Arf... the only place I'm seeing that linked is buried in the list of
post-Kona mailing papers. In particular, it's NOT the "latest publicly
available draft" at http://www.open-std.org/jtc1/sc22/wg21/ (i.e. the
latest publicly available draft... isn't).

Thanks.

--
Matthew

Ville Voutilainen

unread,
Dec 31, 2015, 11:51:28 AM12/31/15
to ISO C++ Standard - Future Proposals
On 31 December 2015 at 18:41, Matthew Woehlke <mwoehlk...@gmail.com> wrote:
>> Perhaps n4567?
> Arf... the only place I'm seeing that linked is buried in the list of
> post-Kona mailing papers. In particular, it's NOT the "latest publicly
> available draft" at http://www.open-std.org/jtc1/sc22/wg21/ (i.e. the
> latest publicly available draft... isn't).


The whole site is a volunteer effort, the maintainer doesn't necessarily always
manage to keep it up-to-date. Always look at the most recent mailing
for the most recent draft. Chances are that the reason the link to the
"most recent draft"
is not kept up to date is that ISO has restricted its rules, and N-papers
shouldn't even be available outside 'LiveLink' (don't ask, don't tell).

Vicente J. Botet Escriba

unread,
Dec 31, 2015, 12:15:53 PM12/31/15
to std-pr...@isocpp.org, isocpp Ranges Study Group
Le 31/12/2015 15:52, Matthew Woehlke a écrit :
> On 2015-12-31 06:45, Vicente J. Botet Escriba wrote:
>> I believe that introducing conversion would make things more complex
>> that necessary, IMO we need that we have the same type.
>> [...]
>>
>> 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; }>
>>
>> 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*>.
> Uh... yeah. Only, why "almost"?

KEYWORD { int ; const char*; } wouldn't have default constructor, member swap, constructor from std::pair, ...

>
> The whole justification for using anonymous structs *instead of* tuple
> is that the values are *named*. If they're not named, you've defeated
> the purpose. In that case, why not just use tuple?
You can. My intent was to make evident that KEYWORD { T ; U; } could be
another way to represent almost a std::tuple.
>
>> Conversion from two such types that have the same data member types but
>> with different data member names would be desirable.
> Uh... no. No it isn't. (Not implicitly, at least! See my other post for
> an example why.)
Agreed, your example polar/cartesian is an example where the tags are
giving meaning to the type. However I don't believe this is the correct
way, the double should be different in polar and cartesian.

Note also that we have conversion from pair to tuple and this doesn't
surprise anyone.
> And I'd still prefer to see this, for doing so explicitly:
>
> struct { int a; double b; } x;
> struct { int c; double d; } y;
> x = ...;
> y = { [*]x };
Yes, I remember the (python like) thread.
>> 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.
> Now *that* would be useful. (Hmm... actually, given reflection,
> shouldn't this be possible with the addition of an appropriate template
> ctor to std::pair? Naturally, std::tuple should get the same treatment.)
The problem I see is how to name the fields :(
This is why I said for any field names and I don't know how to do it yet if

struct { int a; double b; }

and

struct { int c; double d; }

are different types, or if they don't have some kind of automatic conversion.

> ...or we could just hand-wave the details and leave it to compilers to
> figure out (which might anyway be more efficient).
>

Vicente

Matthew Woehlke

unread,
Dec 31, 2015, 1:03:14 PM12/31/15
to std-pr...@isocpp.org
On 2015-12-31 12:15, Vicente J. Botet Escriba wrote:
> Le 31/12/2015 15:52, Matthew Woehlke a écrit :
>> On 2015-12-31 06:45, Vicente J. Botet Escriba wrote:
>>> Conversion from two such types that have the same data member types but
>>> with different data member names would be desirable.
>>
>> Uh... no. No it isn't. (Not implicitly, at least! See my other post for
>> an example why.)
>
> Agreed, your example polar/cartesian is an example where the tags are
> giving meaning to the type. However I don't believe this is the correct
> way, the double should be different in polar and cartesian.

I'm not sure I agree, but at worst that just means I gave a bad example.
There surely exists aggregates with the same layout but significantly
different semantics. (Heck, there exist aliases for the same *primitive
types* that have significantly different semantics. Just consider the
reasons that folks want strong type aliases.)

> Note also that we have conversion from pair to tuple and this doesn't
> surprise anyone.

Yes, but I also expect that; neither pair nor tuple assigns any meaning
to their members other than what is imparted by the members' indices.
IOW, std::pair<T, U> and std::tuple<T, U> are semantically equivalent.
This is *NOT* necessarily true for two aggregates with the same layout.
(It also, to be fair, isn't necessarily *false* either, but...)

>> And I'd still prefer to see this, for doing so explicitly:
>>
>> struct { int a; double b; } x;
>> struct { int c; double d; } y;
>> x = ...;
>> y = { [*]x };
>
> Yes, I remember the (python like) thread.

:-)

The main point there, however, should be that it (IMHO) makes more sense
to have a really cheap (to type) way to explicitly force conversion
between different-but-same-layout aggregates. Declaring aggregates to be
tuple-like already gets us partway there, as provides some hope that
we'll eventually have something like the above. (That said, I'd bet
there are ways to do this with library tricks already.)

If we have such a method, the "need" for implicit conversions is
significantly reduced.

To be clear, I *do* think it would be useful to have a way to
*explicitly* convert between layout-compatible aggregates. I just feel
that the key word there is "explicit".

--
Matthew

Vicente J. Botet Escriba

unread,
Jan 1, 2016, 7:24:28 AM1/1/16
to std-pr...@isocpp.org
Le 31/12/2015 19:03, Matthew Woehlke a écrit :
> On 2015-12-31 12:15, Vicente J. Botet Escriba wrote:
>> Le 31/12/2015 15:52, Matthew Woehlke a écrit :
>>> On 2015-12-31 06:45, Vicente J. Botet Escriba wrote:
>>>> Conversion from two such types that have the same data member types but
>>>> with different data member names would be desirable.
>>> Uh... no. No it isn't. (Not implicitly, at least! See my other post for
>>> an example why.)
>> Agreed, your example polar/cartesian is an example where the tags are
>> giving meaning to the type. However I don't believe this is the correct
>> way, the double should be different in polar and cartesian.
> I'm not sure I agree, but at worst that just means I gave a bad example.
> There surely exists aggregates with the same layout but significantly
> different semantics. (Heck, there exist aliases for the same *primitive
> types* that have significantly different semantics. Just consider the
> reasons that folks want strong type aliases.)
After reflection, I agree with you and the tags should be part of the types.

Vicente

Vicente J. Botet Escriba

unread,
Jan 2, 2016, 6:25:20 AM1/2/16
to std-pr...@isocpp.org, mwoehlk...@gmail.com
Le 31/12/2015 13:28, Giovanni Piero Deretta a écrit :
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) ...
Well, I'm not sure. Relaxing  [dcl.fct]/11 seems to need some kind of type equality. Having nominal typing seems to make things simpler, but maybe I'm wrong.

 

[...]
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.
After some more thought and Andrew polar/cartesian example, I believe that this structural conversion should be explicit.

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.
I need either structural typing or nominal typing equality. Which one is the better? For the time been I'm going towards nominal 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
Yes, [DESIGNATED_INIT] are on the list of things to consider and associate the type to an aggregate initializer

auto t =  {`first = 10, `second = "hello"s};


[DESIGNATED_INIT]: https://gcc.gnu.org/onlinedocs/gcc/Designated-Inits.html "Designated initializers"


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
I'm missing the file

#include "q.hpp"


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.
Please, could you elaborate. I don't see what are you looking for.

Vicente

Nicol Bolas

unread,
Jan 2, 2016, 10:07:15 PM1/2/16
to ISO C++ Standard - Future Proposals, ran...@open-std.org
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.

Vicente J. Botet Escriba

unread,
Jan 3, 2016, 5:35:33 AM1/3/16
to isocpp Ranges Study Group, ISO C++ Standard - Future Proposals
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:
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.
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.

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:

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.
Do you prefer to name it ResultType_for_func_and_other_func? No, off course.

IMO product types are as natural as parameters of a function than as result of a function. We don't want to name them.



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 ;-)


And in C++, we declare two types to be the same by giving them the same name.
Right.


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.
Agreed.


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.

I don't agree here. Each function independently have its output. If the output has already a name in the domain, we just use it. Otherwise there is no need to find artificial names.

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, ...

Granted unnamed structs can not be used as such (this is already admitted in previous posts).
So the question I'm raising is if we want something as simple as possible (at the user level, not at the standard level)  to mean multiple values returned by a function

After some insignt, I believe that even the keyword could be removed ;-)

    {int x; float y} other_func(int a, int b)     {...}

    auto other_func(int a, int b) -> {int x; float y}    {...}

Note the symmetry between parameter and results (almost symmetric).

Vicente

P.S. Note that I'm not against giving a name to an aggregation when the aggregation has more meaning than just its parts.


Nicol Bolas

unread,
Jan 3, 2016, 12:12:21 PM1/3/16
to ISO C++ Standard - Future Proposals, ran...@open-std.org
On Sunday, January 3, 2016 at 5:35:33 AM UTC-5, Vicente J. Botet Escriba wrote:
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:
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.
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.

See, that right there is what I do not believe. It has not been my experience that the name of such an aggregation "is not meaningful".

Remember, we're not talking about a throwaway MRV case where you would use a tuple nowadays. We're talking about an MRV case where the individual return values need a semantic name, where the mere type or position of the value does not indicate its meaning. That's not all or even most MRV cases.

`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:

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.
Do you prefer to name it ResultType_for_func_and_other_func?

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.

Vicente J. Botet Escriba

unread,
Jan 3, 2016, 3:27:25 PM1/3/16
to std-pr...@isocpp.org, ran...@open-std.org
No. I'm talking also of those cases, as I don't see a case where the position is enough to transport the semantics. DO you have an example of those cases where pair or tuple would be superior?


`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.
Yes, these are the common cases that merit a MRV with names.


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 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.


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.
Agreed.

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.
This is the same for me.

That the aggregation exists solely as a workaround to the lack of MRV support in the language.
Exactly ;-)


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.
Ugh, and access with first and second :( Or are you suggesting the use of get<Iterator>(res) and get<did_insert>(res)?


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.
A function returning twice the same type ?

`map::insert` is problematic only because it use `bool`; if it had used a more expressive typename, it would have been fine.
Not for me, but I can understand that you find it 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.

and when you see in the code res.second it is evident to you that this mean the pos_offset, eh?


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.
I don't agree. The types can be the same and we need something else that gives the semantic.


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:

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.
Do you prefer to name it ResultType_for_func_and_other_func?

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.
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?

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?
A change on the standard library, of course.

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 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.

 
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.
How many time do you then return an aggregate by value?

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.
Move semantic, RVO.


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.
Someone said that they are the same?

Structured binding doesn't even have to do with naming return values, since it's the receiver who is giving it a meaningful name.
It has to be with MRV. Pattern matching is another way to access these values and Structural binding is just a particular case.

A name that notably could be wrong.
You can always use the bad name, even with named fields. As you can use .first instead of .second :(

Indeed, if you call a function that returns named MRVs, you shouldn't use structured bindings on the return value (most of the time).
We are on a C++ standard proposals ML. What each one should do or not do is not important here.

You'd just use the aggregation directly, accessing the member fields using the convenient names.
Let see how do people use them when available.


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.

Naming the type will not prevent to make use .first/.second for pair or the even less friendly get<0>, get<T> for tuple. Again, anything not related to the satndard is out of the scope of this ML. Maybe GSL.

Vicente

Nicol Bolas

unread,
Jan 4, 2016, 11:31:24 AM1/4/16
to ISO C++ Standard - Future Proposals, ran...@open-std.org

How do you define "superior"?

I would say that if the `pair` returned from `map::insert` were `pair<iterator, did_insert>` this would be sufficient information for the user to figure out what's going on.

Would a `struct{ iterator insert_loc; bool did_insert}` return value be "superior" to that? By some measures, yes. But it would also be longer; it'd require more reading. Does that make it inferior to the other? Who decides?

"Good enough" is good enough to me. `pair<iterator, did_insert>` is good enough to be clear to the reader what the return value means. So having a superior solution is unnecessary, even if we accept that your proposed solution is "superior".

It stops being a "need" and becomes a "maybe nice to have." And we shouldn't make major language changes for a "maybe nice to have."


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 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.

And exactly how would this be changed by `struct{iterator insert_loc; bool did_insert}`? We'd still have to bikeshed about both the name of the individual fields and the order of those fields (yours put the "status" first). So what if you add one more thing to the pile of bikeshedding?

Most importantly of all... who cares? Any (not-deliberately-stupid) name you pick will be preferable to the impenetrable `pair<iterator, bool>`. Stop caring so much about whether a name is perfect. Especially when you care to the point where you start arguing that we shouldn't have names at all just to prevent bikeshedding. Just pick one and move on.

Nothing annoys me quite so much as name bikeshedding. Unless there's a genuine linguistic/syntactic problem, or the name is provably ambiguous, or there is an explicit and well-defined naming convention that would be violated, just take the first one suggested and move on.
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.
Ugh, and access with first and second :( Or are you suggesting the use of get<Iterator>(res) and get<did_insert>(res)?

I don't care what you use to access the values (and I'll care even less with structured binding). What I care about is what the reader of the function signature sees. The user sees that the function returns an `iterator` value and a `did_insert` value. From those type names, coupled with the nature of the function, the user can deduce what the values mean.

And therefore, the function has improved clarity over `pair<iterator, bool> insert()`.
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.
A function returning twice the same type ?

OK, why is it "returning twice the same type"? I don't want some arbitrary "here's a thing that could happen". I want a concrete function that performs a useful operation where "returning twice the same type" is a legitimate operation.

For instance, `stoul/stoull`, if we want to replace the optional parameter with a return value. But I already covered that one with a type alias.

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?

If it were magically available today across all compilers and had been for years (and you reasonably resolve all of the various problems with the idea), sure, I'd probably use it. Occasionally. I'm not an "always use auto" zealot, so I prefer not to do things that require users to use `auto` (which is why I typedef my `tuple`s). So I would avoid this construct unless there was good reason not to.

But in any case, the question is irrelevant. You're not proposing that we go back in time and stick it into C++98. You're proposing it now.

Well, we don't need it. With better aliasing support on the sender's side, and structured bindings on the receiver's side, the number of cases where positional return values would be ambiguous would be few if ever.

So the improvements in legibility or program clarity are minimal over the tools we're already going to be getting (hopefully).
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?
A change on the standard library, of course.

This small improvement to clarity is not important enough for a breaking change. If we get new versions of containers for any "STL2" type thing, then obviously this should be resolved. But the committee would rightly reject any breaking proposal just to fix 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 don't really know what you're getting at with this statement.
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.

OK, why do you expect that? Equally importantly, why do you think "a lot of the current code is written using references as output parameters"? There really aren't that many functions out there which try to return multiple values, whether via tuples, reference parameters, or whatever.

That's not to say that it doesn't happen. But we are hardly being overrun by functions that have MRVs of some form.
 
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.
How many time do you then return an aggregate by value?

... I don't understand your logic.

On the one hand, you agreed with my notion that named MRVs are about cases where "the aggregation itself is meaningless". And now you seem to be suggesting that any aggregate returned by value is meaningless and ought to be an unnamed struct.

Do you not see how those are different?

I return lots of things by value. Those "things" might be basic types, classes or simple aggregates. But in all cases, they are a single, conceptual value.

If I return a 3D vector struct, I am returning a 3D vector. It is a single value that happens to have multiple elements to it. It is not 3 separate return values, even if the type is classified as a C++ aggregate.

I return plenty of aggregates by value. And in none of those cases would I use an unnamed aggregate, because those aggregations have meaning. In virtually every case, there are functions that take them as parameters or types that use them.

What often happens to me is that I will start developing with what seems like an MRV. But logical use of that API turns it into a meaningful aggregate. For example, I have an class that represents a set of images. I need to get the width, height, possibly depth, etc of the image. At first, I considered just a `GetWidth`, `GetHeight`, etc. But then I decided to have a `GetDimensions` function that returns an aggregate of all of them.

At which point... I started using that `Dimensions` type in many places. Instead of having functions that take a width/height, I just pass `Dimensions`. And internally in the ImageSet class, I store a `Dimensions` object; it just makes things simpler.

I have rarely had the need to return some aggregation that had no fundamental meaning beyond that function.
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.
Move semantic, RVO.

That's not an answer.

In order for MRVs to be more frequent now in your code than they were before, you have to actually be writing functions that return more than one conceptual thing to the user. That is, you're not returning a value-struct which has some innate meaning; your function is returning two or more conceptual values which have little if any coupling (like `map::insert`).

What kinds of functions are you writing that require you to return multiple, uncoupled values?
Indeed, if you call a function that returns named MRVs, you shouldn't use structured bindings on the return value (most of the time).
We are on a C++ standard proposals ML. What each one should do or not do is not important here.

... what?

How a feature ought to be used by people is very important for discussion of a proposal. We don't want to add features to the language that make it worse.

Or have we forgotten the `operator&`/`addressof` debacle?

Matthew Woehlke

unread,
Jan 4, 2016, 12:08:56 PM1/4/16
to std-pr...@isocpp.org
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 :-).

(¹ Except when it isn't, and they're already named.)

> that we need to have specialized syntax to do this?

"No"... but IRTVPDUA especially isn't *just* about this, but about any
instance where repeating the typename is "hard". And the [dcl.fct]/11
prohibition is just unnecessary. (Also, what about lambdas; will you
create a local struct

> 9 times out of 10, other functions will want to return that same
> type. So you're going to need a named type.

Such functions are likely *already using* named types, and have been
doing so for a long time (pre-C++11 even).

> 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*.

This sounds like a good reason to continue to make that case difficult.
Note that the IRTVPDUA proposal intentionally does not attempt to
"solve" this case.

That said, I can give you an excellent reason why you may *want*
something like this to work (named types or not, even)... library
boundaries. Perhaps other_func() is in a library ("A") that is used
privately by the library providing func() ("B"), whose implementation
details should not be leaked to users of "B".

> 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.

--
Matthew


Matthew Woehlke

unread,
Jan 4, 2016, 12:41:13 PM1/4/16
to std-pr...@isocpp.org
On 2016-01-03 12:12, Nicol Bolas wrote:
> 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.

This, FWIW, is exactly the sort of use case I expect, and indeed is
exactly the sort of use case I've seen in languages that have first
class MRV's (i.e. Python). That is, the values are associated only in
that they are both outputs of a particular function, but don't otherwise
make much sense together.

> enum class did_insert : unsigned char
> {
> no_insert = 0,
> inserted,
> };
>
> pair<iterator, did_insert> insert(...);

...but this is exactly the same problem; you are creating a type whose
sole purpose is to work around the absence of named MRV's. In fact, I
would argue this is *worse* than named MRV's, because "bool inserted" is
more concise (and easier to work with, as it has the more logical type
'bool' rather than needing to be coerced into a bool). It's also no help
if the type in question is something like 'int'. (At best, you would
need to introduce type aliases...)

Anyway, even if other solutions are possible, they still entail work.
The "work" to lift the restriction against returning anonymous structs
in the standard is... the removal of two words. 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.

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(...);

The anonymous struct is only "longer" if you exclude the cost of the
type aliases / enums / etc. Even then, it's only (notably) longer by the
names of the values, which might have been given *anyway*:

pair<iterator /*inserted_value*/, bool /*was_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.

I ended up doing something different (apparently before attempting to
compile the code in question), but I recently was toying with using an
anonymous struct return in another project.

"Almost never" is a slippery argument. There are a lot of corners of the
standard library that I have *literally* never used, especially newer
features, but their inclusion was still felt to be justified.

(¹ On that note, I literally just a few days ago wrote my own expected
because I really, really needed it for an API I was writing. I
*probably* would not have used anonymous structs, if they'd been available.)

> 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).

> And if there was a need, then I returned a Lua table with named fields.

How is that unlike returning an anonymous struct? ;-)

> You say that you use MRVs more frequently than you used to. Why?

...because the pain threshold for doing so is being lowered.

--
Matthew

Nicol Bolas

unread,
Jan 5, 2016, 1:12:32 PM1/5/16
to ISO C++ Standard - Future Proposals, mwoehlk...@gmail.com

No, it isn't.

As previously discussed, each function that returns an unnamed struct will be returning a different type, even if they look the same. Even declaring and defining such functions in different places has to use reworked language, because technically speaking, the declaration and definition are returning different types. Which isn't allowed in C++.

So the only way to make this a "two word" change is to restrict its use to only being for inlined functions. If you want to be able to declare and define them in different places, then you must either add your `auto` feature or you have to make unnamed types the same for the same function overload. Either way adds wording, and therefore it's not a "two word" change.

Given that:

Nor is it expected that
the work to add this "feature" to compilers will be difficult.

I'd say it's rather premature to declare how difficult it would be to add this feature. Especially since your `auto` feature has some technical issues left to work out (as I understand it).

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.

You don't want to use an alias or an enum with `pair`? You think it won't be as clear to the reader at the destination site? Fine. But why does the return struct have to be unnamed?

Also, let's not forget Option 4:

auto {it, inserted} = map.insert();

if(inserted) {...}

I like that better than any of them. It lets the person writing the code decide what the names mean. And this option is likely coming to C++, one way or another.

So C++ has Option 3 via a named type. C++ will have Option 4 via structured binding. So where is the space for your unnamed type?

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(...);

You forget: there are dozens of functions that would use this. There are two overloads of `insert`, two overloads of `insert_or_assign`, `emplace`, and two overloads of `try_emplace`. That's 7 functions on std::map alone. std::set, std::unordered_map, and std::unordered_set all have similar functions that use this type.

Those "six lines"? They only have to be typed once.

Ultimately, this should have been a named type all along. Having to type `struct{iterator it, bool inserted}` dozens of times makes it exceedingly easy to make a typo.

> 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.

First, I disagree that "almost any function with output parameters" ought to use MRVs. `optional` and `expected` cover a good number of those cases. Most of the rest are either things like `stoi` where it's returning extra information, or places where you really need an output parameter. You're filling in memory that someone else allocated. Nowadays, we might use a `span<unsigned char>` or whatever, but it's still ultimately an output parameter no matter how you specify it.

Second, do we want language features like this for "corner cases?" How many such corner cases do you encounter?

And do we need such corner case features when we have people practically frothing at the mouth to abuse it?

> 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*.

Agreed but... that doesn't explain why we specifically need unnamed structs to fill that gap.

There are two major problems with using `tuple` to fill that gap.

1) It is exceedingly painful for the receiving code to use.

2) It provides no useful semantic information beyond the typenames.

Structured binding solves problem #1. But it also solves most of problem #2, at least from the perspective of someone reading the receiving code.

So the space for unnamed structs would seem to be for reading the function declaration. Except... it could just be a named struct. And as you agreed to in your next post, it ought to be a named struct if the aggregation is used in multiple places.

So the space for this feature seems decidedly small. It must be a one-off usage where typenames aren't enough to deduce the meaning.

> 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).

... How is it more self-documenting?

auto x = foo();

What is `x`? Is it one value? Twenty? I don't know; I have to go look it up.

tuple<int, float, std::string> x = foo();

I wouldn't go so far as to call that "self-documenting" but at least it has substance. It gives me the chance to at least guess at what the interface might mean, given the name of the function called and the return types.

pair<iterator, did_insert> x = mapy.insert(...);

Looking at that, I can make educated guesses about what those fields are. Odds are good that those guesses will be correct. That's far more "self-documenting" to me than `auto x = foo();`. So is:

auto {it, did_insert} = mapy.insert(...);

But at the very least, there could be this:

Typename x = foo();

This has less substance, but I at least have two pieces of information I can use to figure out what this means. I have the function name and a type name.

> And if there was a need, then I returned a Lua table with named fields.

How is that unlike returning an anonymous struct? ;-)

Because Lua's not a static language. In Lua, I can do this: `foo(bar())`. If `bar` returned 5 values via positional MRV, `foo` would take those 5 values. If `bar` returned a table with named fields, that's what `foo` would get. So regardless of how I return those MRVs, I have lost none of the expressive power of the language.

If I return an unnamed struct, I cannot chain calls with it. I must use a named type in that circumstance. So unnamed structs have a restriction to my expressive power which named structs and `tuple`s do not have.

---------
In reply to your next message:
--------


On Monday, January 4, 2016 at 12:08:56 PM UTC-5, Matthew Woehlke wrote:
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?

I can "imagine" a lot. That doesn't mean it's worth adding a feature to the language for.

Show me 5 actual methods in the same class which return MRVs that follow the 2 principles that I outlined and you agreed with. That is, tuples would be inappropriate because the typenames are non-indicative of the meaning. And no other code uses that same aggregation of values with that meaning.

`map::insert` and its ilk don't follow principle 2, because multiple overloads and different functions return the same aggregation which has the same meaning. So they're not examples.

Naming things is *hard*. Naming multiple return values¹ is *especially*
hard, and bloats the API unnecessarily.

I'm sorry, but I have a problem considering inserting a typename to constitute "bloat".

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.

No, we're talking about the cost of naming one-off structures who's typenames don't provide sufficient semantic information to know what they mean. That's what you agreed to when you agreed with principle #1.

The only example I've run into (in the standard library, at least) is `stoi` and its ilk. What other real code examples can you give?

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 :-).

Except that we'll also have to deal with people who won't name types that ought to have names. Just as we have to deal with tuple-happy people.

Giving people carte blanche to force me to type `auto` more is not something I look forward to.

> 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.

Maybe you've mixed up the threads, but we're talking about returning unnamed structs. Your `auto` feature is important to that discussion only in that it provides a way to declare and define such functions. But your `auto` feature is not strictly necessary to it, nor (as you point out) is the justification for your `auto` feature solely to support unnamed structs.

So that's a tangential argument.
 
> 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.

How? It makes returning `tuple`s more digestible because the writer of the receiving code can give the unnamed fields a semantic name (and not have to type a bunch of pointless `get<>` calls or use `std::tie`). It makes returning unnamed structs less useful because `tuple` becomes a legitimate MRV solution, rather than a massive eyesore. Readers of the code have a semantic name they can see (even if it's not the one the writer of the function might have picked).

Matthew Woehlke

unread,
Jan 5, 2016, 2:31:06 PM1/5/16
to std-pr...@isocpp.org
On 2016-01-05 13:12, Nicol Bolas wrote:
> On Monday, January 4, 2016 at 12:41:13 PM UTC-5, Matthew Woehlke wrote:
>> Anyway, even if other solutions are possible, they still entail work.
>> The "work" to lift the restriction against returning anonymous structs
>> in the standard is... the removal of two words.
>
> No, it isn't.

If that's true, I would sincerely appreciate *constructive* feedback as
to what other changes need to be made.

However...

> As previously discussed, each function that returns an unnamed struct will
> be returning a *different type*, even if they look the same.

I've repeatedly stated that I don't consider this a problem; that, in
fact, I consider it *a good thing*.

> Even declaring and defining such functions in different places has to
> use reworked language, because technically speaking, the declaration
> and definition are returning *different types*. Which isn't allowed in C++.

I certainly concede that loosening [dcl.fct]/11 runs into this problem.
*IF* we do not also have IRTVPDUA. This is why the latter is (currently)
part of the proposal for the former. It indeed does not make sense to
have the latter without the former.

> So the only way to make this a "two word" change is to restrict its use to
> only being for inlined functions.

Okay, then to be fair, I will clarify that when I make that statement, I
am considering the change that is needed *in addition* to the IRTVPDUA
changes (which have their own additional justification besides loosening
[dcl.fct]/11).

> Given that:
>
>> Nor is it expected that the work to add this "feature" to
>> compilers will be difficult.
>
> I'd say it's rather premature to declare how difficult it would be to add
> this feature. Especially since your `auto` feature has some technical
> issues left to work out (as I understand it).

There is a non-trivial issue (related to templates) that does still need
to be specified. I would however consider it "worked out".

I would also encourage you to check the current version of the proposal
for an example of how both GCC and ICC *already do support* returning
anonymous structs from functions. (They do. Really. It's necessary to
employ a rather circuitous technique to "name" - via template type
deduction - the return type, but there appears to be no fundamental
problem returning a struct that was defined as anonymous. This suggests
that the overhead of loosening [dcl.fct]/11 either for inline
definitions or given IRTVPDUA also, may be minimal or even trivial. Then
there is MSVC, which just supports this already, although some
modification may be required to align that support with how it would be
specified in the standard.)

> You don't want to use an alias or an enum with `pair`? You think it won't
> be as clear to the reader at the destination site? Fine. But why does the
> return struct *have to be* unnamed?

Why does it *have to be named*? Naming things has a cost, too.

> You forget: there are *dozens* of functions that would use this.
> There are [list of 7 examples]. std::set, std::unordered_map, and
> std::unordered_set all have similar functions that use this type.
>
> Those "six lines"? They only have to be typed *once*.

Really? std::[unordered_]{map,set} will all use the same struct defined
in namespace std? (As a template struct, I guess?) Otherwise, your
example is just about break-even.

Granted, perhaps this *particular example* should use a named struct.
What about places that *don't* have many functions returning the same
value set?

>>> 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)?
>>>
>>> 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.
>
> First, I disagree that "almost any function with output parameters"
> ought to use MRVs. `optional` and `expected` cover a good number of
> those cases.

For the purposes of the above statement, I am considering any complex
type a form of MRV. You'll note that I did state that std::expected
should help significantly.

> Most of the rest are either things like `stoi` where it's returning extra
> information

Exactly.

> Second, do we want language features like this for "corner cases?" How many
> such corner cases do you encounter?

We *already have* a language feature. We have a prohibition on defining
types in certain contexts. If IRTVPDUA is accepted, there will be little
reason for that prohibition. This isn't *adding* a feature for corner
cases, it's *removing* a corner case in the specification. In a strict
reading of the standard, that's a *simplification*. It may well be a
simplification in compilers, also. We've already seen that the
limitation is not uniformly enforced, which suggests that at the
compiler implementation level, it may well be artificial. If so,
removing it would also simplify those compilers in which the limitation
is artificial.

> And do we need such corner case features when we have people practically
> frothing at the mouth to *abuse* it?

I don't see this.

>> 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*.
>
> Agreed but... that doesn't explain why we specifically need *unnamed*
> structs to fill that gap.

What else will fill it? If named structs could do so, why don't we see
that already?

> There are two major problems with using `tuple` to fill that gap.
>
> 1) It is *exceedingly* painful for the receiving code to use.
>
> 2) It provides no useful semantic information beyond the typenames.
>
> Structured binding solves problem #1. But it also solves most of problem
> #2, at least from the perspective of someone reading the receiving code.

...but it doesn't help someone that needs to *write* said code.

> So the space for this feature seems decidedly small. It must be a one-off
> usage where typenames aren't enough to deduce the meaning.

...and the cost is also small. It involves the removal of a whole two
words from the standard¹. It removes a corner case of a language feature
that is valid in many contexts being unnecessarily prohibited in a
particular context. It potentially simplifies compilers by removing the
need for them to implement an artificial restriction.

(¹ And I'll reiterate that I am assuming adoption of IRTVPDUA. To be
even more clear, if IRTVPDUA is *not* adopted, I have no intention of
pressing for relaxing [dcl.fct]/11 without IRTVPDUA.)

>> 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).
>
> ... How is it more self-documenting?
>
> auto x = foo();
>
> What is `x`? Is it one value? Twenty? I don't know; I have to go look it up.

I'm talking about the function declaration. Sure, 'x' in the above
example does not have an obvious meaning, but that's true no matter how
yo return MRV's.

With a struct, when you use x, e.g. 'x.converted_value', the usage is
much clearer than 'get<0>(x)' or even 'get<int>(x)'. It's also much
clearer from the declaration of 'foo()' what is being returned, compared
to returning e.g. std::tuple.

> Typename x = foo();
>
> This has less substance, but I at least have two pieces of information I
> can use to figure out what this means. I have the function name and a type
> name.

If that type name is something like tuple<int, double> or foo_result,
how exactly does that help?

In the latter case you probably need to go look at the definition of
foo_result, which is likely to be exactly as difficult as looking at the
declaration of foo() and seeing that it returns '{ int whole_part;
double fraction_part; }'.

>> And if there was a need, then I returned a Lua table with named fields.
>>
>> How is that unlike returning an anonymous struct? ;-)
>
> Because Lua's not a static language. In Lua, I can do this: `foo(bar())`.
> If `bar` returned 5 values via positional MRV, `foo` would take those 5
> values. If `bar` returned a table with named fields, that's what `foo`
> would get. So regardless of how I return those MRVs, I have lost none of
> the expressive power of the language.

But that's not likely to ever work in C++, unless there exists an
overload of foo() that takes an appropriate std::tuple.

BTW, that's not possible in Python. In Python, you would have to write:

foo(*bar())

And as I've stated elsewhere, what is so terrible about writing (in C++):

foo([*]bar())

...? This would further allow (as it does in Python) to "merge" multiple
parameter packs, and clarifies when unpacking is intended, and when a
pack should be passed as a single object. It also works just fine if
bar() returns a struct (anonymous or not). Or a tuple. Or anything
"tuple-like".

> If I return an unnamed struct, I cannot chain calls with it. I must use a
> named type in that circumstance. So unnamed structs have a restriction to
> my expressive power which named structs and `tuple`s do not have.

Again, I don't see this as a legitimate argument. How often do you see,
given a function like 'foo(int, double)', that folks are starting to
also always write an overload 'foo(tuple<int, double>)'? (Moreover, I'll
go out on a limb and say that if that ever *does* become prevalent,
we've screwed up majorly.) What about foo() and bar() being in different
libraries; why do you expect that foo() will even *know about* the
library from whence bar() comes, much less take the struct that bar()
returns as an alternative to individual parameters?

The whole point of these directions is to move *away* from being locked
into passing multiple values around as specific types, in favor of being
able to dynamically unpack and repack value collections. (We already
have repacking in the form of aggregate initialization. We're working on
unpacking. P0144 / P0151 should fix one aspect, but IMO we still need
something like '[*]'.)

I think this goes back to your insistence from back when that value
collections be some sort of first class citizen *separate* from any
existing notions of complex types. I just don't see that happening. The
language is moving toward fluid conversion between value collections
implemented as aggregates (or otherwise as traditional complex types),
and I can't say that I've seen anyone else supporting your views.

Incidentally, I (briefly) address exactly this point in the current
version of the IRTVPDUA paper.

> Show me 5 actual methods in the same class which return MRVs that follow
> the 2 principles that I outlined and you agreed with. That is, tuples would
> be inappropriate because the typenames are non-indicative of the meaning.
> And no other code uses that same aggregation of values with that meaning.

Why the same class? I'd need five different names whether or not their
in the same class.

>> Naming things is *hard*. Naming multiple return values is *especially*
>> hard, and bloats the API unnecessarily.
>
> I'm sorry, but I have a problem considering inserting a typename to
> constitute "bloat".

It's one more name polluting the name space for a *single use*. It's one
more type that shows up in the documentation (and, potentially, *needs
to be documented*²). It's one more type that users can construct, when
doing so may make no sense.

If you disagree, I propose that you assign aliases to the return values
of *every function you write*. It's the exact same principle, differing
in scale, but not in principle.

(² Note that I'm considering it easier to document return values than
types with their members. Because... it usually is.)

>> See also Vicente's response to this point. Remember, we're talking about
>> the cost of naming *one-off* structures.
>
> No, we're talking about the cost of naming one-off structures who's
> typenames don't provide sufficient semantic information to know what they
> mean. That's what you agreed to when you agreed with principle #1.

We're talking about naming one-off structures. As opposed to structures
used many times.

>> [IRTVPDUA stuff...]
> Maybe you've mixed up the threads [...] that's a tangential argument.

Yes, I think you're correct in that spot. Sorry.

>> Structured binding, if anything, makes [returning anonymous
>> structs] *more* useful.
>
> How?

Because the pain of unpacking the struct into its component values is
removed. Structured binding encourages returning MRV's (I think we agree
on this?). This means that people are more likely to return ad-hoc
complex types. These developers are going to fall into three categories:

1. Folks that can't be bothered to name *anything*. These will use
std::tuple.
2. Folks that are anal about naming. These will use named structs.
3. Folks that will name things *if it's easy*.

Camps 1 and 2 aren't interesting; they're going to do what they do
anyway. Returning anonymous structs is interesting to camp 3 (which, to
be fair, I will note is that camp to which I belong). If the individual
values are hard to name, they will use std::tuple. If naming
*everything* is easy, they may well do so. The trouble spot is in the
case (which I assert is not unusual) where naming the individual values
is easy, but naming the *aggregate* is hard (example: stoi).

Do you prefer std::tuple or an anonymous struct in this case? I know
which I prefer.

> It makes returning unnamed structs less useful because `tuple`
> becomes a legitimate MRV solution, rather than a massive eyesore.

I completely disagree here. IMO, even at the declaration site, tuple is
an eyesore. I would much prefer to use an anonymous struct over a tuple.
At the very least, I strongly disagree that unpacking lessens the value
of anonymous structs as an MRV mechanism.

--
Matthew

Reply all
Reply to author
Forward
0 new messages