Unpacking syntax for tuples using auto

3,704 views
Skip to first unread message

leszek....@gmail.com

unread,
Feb 27, 2014, 1:17:37 PM2/27/14
to std-pr...@isocpp.org
Currently, given a function that returns a tuple

    std::tuple<int,double> foo() {
        return std::make_tuple(1, 2.0);
    }

one can access the returned values using std::tie

    int a;
    double b;
    std::tie(a,b) = foo();

but then you have to know the return types. Alternatively, without specifying types one can use std::get:

    auto ret = foo();
    auto&& a = std::get<0>(ret);
    auto&& b = std::get<1>(ret);

but then you create a new variable and the whole thing seems unnecessarily verbose. Neither of these solutions seems particularly clean to me.

Would it be possible to introduce a new syntax, which both declares the tuple element variables using the correct type, and assigns to them? Something like

    auto (a,b) = foo();

which would define a and b as int and double, and assign to them the values 1 and 2.0, respectively. This would be roughly equivalent to the behaviour of the second option above, but without the extra variable, and behaving like a single expression-statement rather than several.

An interesting application would be tuple unpacking in ranged-for loops, where one could write

    for (auto(key,value) : get_map()) {
        ...
    }

which would be roughly equivalent to

    for (auto&& __temp : get_map()) {
        auto&& key = std::get<0>(__temp );
        auto&& value = std::get<1>(__temp );
        ...
    }

Arguably this feature is just syntactic sugar, but this sort of tuple unpacking is very common in other languages (cf. Python, clojure), and I believe that it would decrease the verbosity (and increase the clarity) of things like multiple return values, map iterators, zip iterators and other similar constructions. Also, one could make the argument that this hides types, but there seems to be an increasing trend towards type inference in C++ (first auto, now auto return values), so I don't personally thing that this is a problem.

What do people think?

Matthew Woehlke

unread,
Feb 27, 2014, 1:52:20 PM2/27/14
to std-pr...@isocpp.org
On 2014-02-27 13:17, leszek....@gmail.com wrote:
> Currently, given a function that returns a tuple
>
> std::tuple<int,double> foo() {
> return std::make_tuple(1, 2.0);
> }
>
> one can access the returned values using std::tie
>
> int a;
> double b;
> std::tie(a,b) = foo();
>
> but then you have to know the return types. Alternatively, without
> specifying types one can use std::get:
>
> auto ret = foo();
> auto&& a = std::get<0>(ret);
> auto&& b = std::get<1>(ret);
>
> but then you create a new variable and the whole thing seems unnecessarily
> verbose. Neither of these solutions seems particularly clean to me.
>
> Would it be possible to introduce a new syntax, which both declares the
> tuple element variables using the correct type, and assigns to them?
> Something like
>
> auto (a,b) = foo();

Regardless of other considerations, I like this syntax ever so much
better than either of the above. (Looks like Python :-). I was just
thinking the other day how it's a shame that C++ can't handle
multi-value returns as nicely as Python... and here you are proposing
it! I like!)

Not that anyone these days *shouldn't* be using auto, but I wonder,
should we also allow e.g.:

(int a, double b) = foo();

...?

Also, I would like if the following are also valid:

auto const&& (a, b) = foo(); // a and b are both const

auto (const&& a, b) = foo(); // a -> auto const&&, b -> auto

> An interesting application would be tuple unpacking in ranged-for loops,
> where one could write
>
> for (auto(key,value) : get_map()) {
> ...
> }

*OMG*... this could save me from writing iterator-based loops!! :-D

Now we just need functions in std to unpack indexed (std::enumerate) and
key/value (std::iterate?) containers like this... That *could* be a
separate proposal but this suddenly makes them really attractive.

Related: bonus points if 'auto (a,b)' can also be used with std::pair.
Maybe it should be implemented like range-based for, i.e. creates an
anonymous temporary and uses a well-known function ('get<index>') to get
each value. Then as long as there is a std::get<0>(std::pair<...>) this
will just magically work, and can also be extended to user types.

Also, the above would mean the compiler effectively is just generating
code like in your (elided) examples, which should make it much easier to
implement the non-auto-valued and with-type-modifier variants as I
suggested above, since those only entail relatively simple logic to
combine the type-and-or-modifier correctly to form each "longhand"
declaration.

IOW:

[A0] ([A1] N1, [A2] N2, ...) = expr;

...is expanded by the compiler to become:

auto&& __temporary__ = expr;
A0 A1 N1 = get<0>(__temporary__);
A0 A2 N2 = get<1>(__temporary__);
...

...where An are "optional" (as long as either A0 is provided or every
other An is provided). And of course all A0 An must be valid type
specifiers.

(Note that the above implies the same rule as range-for that 'std' is an
associated namespace.)

--
Matthew

leszek....@gmail.com

unread,
Feb 27, 2014, 7:33:42 PM2/27/14
to std-pr...@isocpp.org, mw_t...@users.sourceforge.net
On Thursday, 27 February 2014 18:52:20 UTC, Matthew Woehlke wrote:
should we also allow e.g.:

(int a, double b) = foo();

I was thinking this too, but I'm a little bit worried about having parenthesised expressions as L-values -- I'm not sure how pleased the parser people would be about something like this, especially since (a,b) is a perfectly valid L-value (and not a tuple unpacking). I'm not sure how common it would be for someone to want to unpack a tuple into different types than are found in the tuple, so perhaps this is an example where it's best to optimise for the common case.
 
Also, I would like if the following are also valid:

   auto const&& (a, b) = foo(); // a and b are both const

   auto (const&& a, b) = foo(); // a -> auto const&&, b -> auto

I see no (immediate) reason why not, though I'm not sure how well this would interact with, for example, tuples containing references. Would auto(a,b) cause unnecessary copies? Would decltype(auto) (a,b) need to be a thing?
 
Now we just need functions in std to unpack indexed (std::enumerate) and
key/value (std::iterate?) containers like this... That *could* be a
separate proposal but this suddenly makes them really attractive.

My need for something like std::enumerate was one of the main reasons this proposal started formulating itself in my head.
 
Related: bonus points if 'auto (a,b)' can also be used with std::pair.
Maybe it should be implemented like range-based for, i.e. creates an
anonymous temporary and uses a well-known function ('get<index>') to get
each value. Then as long as there is a std::get<0>(std::pair<...>) this
will just magically work, and can also be extended to user types.

Also exactly what I was thinking, calling get<i> using ADL in a similar way to range-based for's ADL use of begin. We'd probably also need to check if the number of arguments matches the number of variables, which would be done using an ADL of tuple_size.
 
Also, the above would mean the compiler effectively is just generating
code like in your (elided) examples, [...]


   [A0] ([A1] N1, [A2] N2, ...) = expr;

...is expanded by the compiler to become:

   auto&& __temporary__ = expr;
   A0 A1 N1 = get<0>(__temporary__);
   A0 A2 N2 = get<1>(__temporary__);

With the small difference that it all counts as a single statement (single expression?)

Matthew Woehlke

unread,
Feb 28, 2014, 12:46:07 PM2/28/14
to std-pr...@isocpp.org
On 2014-02-27 19:33, leszek....@gmail.com wrote:
> On Thursday, 27 February 2014 18:52:20 UTC, Matthew Woehlke wrote:
>> should we also allow e.g.:
>>
>> (int a, double b) = foo();
>
> I was thinking this too, but I'm a little bit worried about having
> parenthesised expressions as L-values -- I'm not sure how pleased the
> parser people would be about something like this, especially since (a,b) is
> a perfectly valid L-value (and not a tuple unpacking). I'm not sure how
> common it would be for someone to want to unpack a tuple into different
> types than are found in the tuple, so perhaps this is an example where it's
> best to optimise for the common case.

But it's not just the different-types case. It's also type modifiers
e.g. 'const', '&', '&&'.

(I wasn't even thinking *different* types anyway, but specifying the
types explicitly rather than using 'auto'.)

Right now, e.g. '(int a, double b)' is not valid syntax; it seems it
should be possible to detect that the components look like declarations
and treat them as tuple unpacking. But I'm not a parser writer, so it
wouldn't hurt to have someone who is weigh in.

Going further, should:

int a;
double b;
(a, b) = foo(); // foo() -> tuple<int, double>

...be allowed? I'd say 'no' since the above is legal in current C++ if
foo() -> double (or something convertible to double). IOW, only invoke
tuple initialization if some An (referring to syntax below) is non-empty.

>> Also, I would like if the following are also valid:
>>
>> auto const&& (a, b) = foo(); // a and b are both const
>> auto (const&& a, b) = foo(); // a -> auto const&&, b -> auto
>
> I see no (immediate) reason why not, though I'm not sure how well this
> would interact with, for example, tuples containing references.

They should behave exactly as I illustrated later in my previously mail,
i.e. the second is equivalent to:

auto&& __temp = foo();
auto const&& a = get<0>(__temp);
auto b = get<1>(__temp);

So there is no new behavior being introduced here.

> Would auto(a,b) cause unnecessary copies?

This (IMHO) should be exactly equivalent to 'auto a = get<0>(...)',
etc.. If that would cause copies, then so will 'auto (a, ...)'.

> Would decltype(auto) (a,b) need to be a thing?

I'm not sure it is possible; it would imply knowing what tuple or
tuple-like type 'auto (a,b)' was assigned from (e.g. tuple, pair, ...?).

(More interesting: do we allow '(int a, double b)' as a bare
declaration? We *could*, though I don't see a benefit in doing so.)

>> Related: bonus points if 'auto (a,b)' can also be used with std::pair.
>> Maybe it should be implemented like range-based for, i.e. creates an
>> anonymous temporary and uses a well-known function ('get<index>') to get
>> each value. Then as long as there is a std::get<0>(std::pair<...>) this
>> will just magically work, and can also be extended to user types.
>
> Also exactly what I was thinking, calling get<i> using ADL in a similar way
> to range-based for's ADL use of begin. We'd probably also need to check if
> the number of arguments matches the number of variables, which would be
> done using an ADL of tuple_size.

Do we? We could also allow the caller to simply discard unneeded
elements. This would tremendously simplify things (no need to check
tuple_size or to implement it for user types; just rely on get<i> to be
a compile error if no such element i) as far as the implementation.

>> Also, the above would mean the compiler effectively is just generating
>> code like in your (elided) examples, [...]
>>
>> [A0] ([A1] N1, [A2] N2, ...) = expr;
>>
>> ...is expanded by the compiler to become:
>>
>> auto&& __temporary__ = expr;
>> A0 A1 N1 = get<0>(__temporary__);
>> A0 A2 N2 = get<1>(__temporary__);
>
> With the small difference that it all counts as a single statement (single
> expression?)

I think yes, if it matters. (I wasn't thinking of this as an actual code
transformation that would then be subject to rules where it would
matter, but rather the compiler would generate assembly "as if" the code
was as above. IOW, scoping, building of symbol trees, etc. has already
been performed. Actually this works even for range-based for, as the
assignment of the value happens inside the loop. Implying of course that
the range-based for transformations happen first. So, also, similar to
the "as-if code expansion" done to implement range-based for.)

Interesting question: is this a permissible declaration inside of if()?
And if so, what assignment is used as the conditional expression?
(First? Last? All of them?)

--
Matthew

rouslank...@gmail.com

unread,
May 15, 2014, 8:17:03 PM5/15/14
to std-pr...@isocpp.org
I thought of an alternative syntax to consider, which might require less drastic changes to the language. Something like this:

    {auto key; double value} = foo();

which would be conceptually equivalent to:

    struct __anonymous_t__ {
        std::tuple_element<0,decltype(foo())>::type key;
        double value;

        template<typename T1,typename T2>
        __anonymous_t__(const std::tuple<T1,T2> &vals) : key(std::get<0>(vals)), value(std::get<1>(vals) {}
    } __anonymous_v__ = foo();


This way also doesn't require any new rules for how types are specified.

Richard Smith

unread,
May 15, 2014, 9:24:49 PM5/15/14
to std-pr...@isocpp.org
On Thu, May 15, 2014 at 5:17 PM, <rouslank...@gmail.com> wrote:
I thought of an alternative syntax to consider, which might require less drastic changes to the language. Something like this:

    {auto key; double value} = foo();

Unfortunately, this syntax is not likely to be a good fit. The compiler has no idea whether it's looking at a compound-statement or your new syntax until it sees that there's a ';' missing before the '}'. That's a lot of lookahead, and would apply to any statement that starts with a '{' (which is extremely common).

which would be conceptually equivalent to:

    struct __anonymous_t__ {
        std::tuple_element<0,decltype(foo())>::type key;
        double value;

        template<typename T1,typename T2>
        __anonymous_t__(const std::tuple<T1,T2> &vals) : key(std::get<0>(vals)), value(std::get<1>(vals) {}
    } __anonymous_v__ = foo();


This way also doesn't require any new rules for how types are specified.

--

---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposal...@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.

Richard Smith

unread,
May 15, 2014, 9:27:45 PM5/15/14
to std-pr...@isocpp.org
On Thu, May 15, 2014 at 6:24 PM, Richard Smith <ric...@metafoo.co.uk> wrote:
On Thu, May 15, 2014 at 5:17 PM, <rouslank...@gmail.com> wrote:
I thought of an alternative syntax to consider, which might require less drastic changes to the language. Something like this:

    {auto key; double value} = foo();

Unfortunately, this syntax is not likely to be a good fit. The compiler has no idea whether it's looking at a compound-statement or your new syntax until it sees that there's a ';' missing before the '}'. That's a lot of lookahead, and would apply to any statement that starts with a '{' (which is extremely common).

Random though: allow anonymous structs, with initializers:

struct { ... } = foo();

and, more generally, support aggregate initialization and assignment from an expression E by calling get<N>(E).

(Though this would need some finessing to allow 'auto'.)

Andrew Tomazos

unread,
May 15, 2014, 9:54:34 PM5/15/14
to std-pr...@isocpp.org
I think the root problem is that you want to initialize multiple values, by decomposing a *single* expression of compound sequence type (for example structs, arrays, tuples, pairs, etc).

    // Example compound sequence type values:
    struct { A a; B b } s = ...;
    T arr[3] = ...;
    std::tuple<A,B,C> t = ...;
    std::pair<A,B> p = ...;

    // use the above to initialize these with single expression somehow:
    A a; B b;  // = s
    T a; T b; T c; // = arr
    A a; B b; C c; // = t
    A a; B b; // = p

I think you're suggesting:
 
    struct { A a; B b; }  = s;
    struct { T a; T b; T c; } = arr;
    struct { A a; B b; C c; } = t;
    struct { A a; B b; } = p;

But it's not clear if the members of these "structs" are introduced into the enclosing scope.  Unless the names are, then it kind of defeats the purpose doesn't it?

Perhaps a different name for this idea:

    decltie { A a; B b; }  = s;
    decltie { T a; T b; T c; } = arr;
    decltie { A a; B b; C c; } = t;
    decltie { A a; B b; } = p;

The body of decltie consists of syntactically uninitialized declarations placed in the enclosing scope, followed by a single initializer.  The initializer is decomposed with get<N>(E) and initializes each of the contained declarations.

Andrew Tomazos

unread,
May 15, 2014, 9:57:03 PM5/15/14
to std-pr...@isocpp.org
So the OPs example would become:

    decltie { auto a, b; } = foo();

Richard Smith

unread,
May 15, 2014, 10:28:16 PM5/15/14
to std-pr...@isocpp.org
On Thu, May 15, 2014 at 6:54 PM, Andrew Tomazos <andrew...@gmail.com> wrote:
I think the root problem is that you want to initialize multiple values, by decomposing a *single* expression of compound sequence type (for example structs, arrays, tuples, pairs, etc).

    // Example compound sequence type values:
    struct { A a; B b } s = ...;
    T arr[3] = ...;
    std::tuple<A,B,C> t = ...;
    std::pair<A,B> p = ...;

    // use the above to initialize these with single expression somehow:
    A a; B b;  // = s
    T a; T b; T c; // = arr
    A a; B b; C c; // = t
    A a; B b; // = p

I think you're suggesting:
 
    struct { A a; B b; }  = s;
    struct { T a; T b; T c; } = arr;
    struct { A a; B b; C c; } = t;
    struct { A a; B b; } = p;

But it's not clear if the members of these "structs" are introduced into the enclosing scope.  Unless the names are, then it kind of defeats the purpose doesn't it?

I was imagining this working exactly like an anonymous union (or like C's anonymous structs): the names are injected into the surrounding context.

Perhaps a different name for this idea:

    decltie { A a; B b; }  = s;
    decltie { T a; T b; T c; } = arr;
    decltie { A a; B b; C c; } = t;
    decltie { A a; B b; } = p;

The body of decltie consists of syntactically uninitialized declarations placed in the enclosing scope, followed by a single initializer.  The initializer is decomposed with get<N>(E) and initializes each of the contained declarations.

Yes, a different keyword for this might be less confusing. I've also seen this syntax suggested:

< A a, B b > = s;
 
... which doesn't immediately seem to conflict with anything else.

Matthew Woehlke

unread,
May 19, 2014, 12:42:08 PM5/19/14
to std-pr...@isocpp.org
On 2014-02-27 19:33, leszek....@gmail.com wrote:
> On Thursday, 27 February 2014 18:52:20 UTC, Matthew Woehlke wrote:
>> should we also allow e.g.:
>>
>> (int a, double b) = foo();
>
> I was thinking this too, but I'm a little bit worried about having
> parenthesised expressions as L-values -- I'm not sure how pleased the
> parser people would be about something like this, especially since (a,b) is
> a perfectly valid L-value (and not a tuple unpacking).

Actually... is this even an issue? It seems that a declaration in this
context is currently invalid, ergo as soon as the compiler sees '('
followed by a typename (or at worst, further followed by an identifier),
it could "know" that it is dealing with a tuple unpacking.

(As a bonus, we could also newly permit e.g. '(int a) = expr', where
expr could be a tuple (get<0>) or even an int literal.)

This has the benefit of not introducing a new keyword, not requiring a
keyword to invoke (which itself I think is significant), and being
fairly natural at least to programmers familiar with Python, as ()'s are
used there to denote tuples.

As a fallback, I'd be okay with Richard's suggestion of using <>'s
instead of ()'s.

--
Matthew

snk_kid

unread,
May 19, 2014, 5:16:48 PM5/19/14
to std-pr...@isocpp.org, leszek....@gmail.com
What do people think?

I think you've just reinevented pattern matching! which is prevalent in virtually all statically typed functional programming languages. What you're describing in particular is just one type of pattern match, a deconstructing tuple pattern. There are various other kinds of patterns that I recommend checking it out.

I would love to see general pattern matching support added to C++ in the future, it's one of the most sorely, most painfully missed feature when I'm coding in C/C++. Trust me when you've been programming in a language with pattern matching support for a while then go to another without it, it's painful.

It would make the other thread about "named tuples" completely redundant, and to me is a hacky solution to begin with. To me pattern matching is the proper general solution to this and other related problems.

There is a proposal already to bring a tiny subset of pattern matching abilities to C++, specifically a type-case patterns but I really think we should just go the whole way for C++17, that would really evolve the language.

gmis...@gmail.com

unread,
May 19, 2014, 6:03:12 PM5/19/14
to std-pr...@isocpp.org, leszek....@gmail.com
Hi
Yes I was saying on the named tuple thread, that a more expanded set of tools is needed than just adding a name to a tuple. I'm pleased this thread has popped up that seems to be getting into those things. The discussion needed to be more wide ranging than it was before now it is so I'm pleased about that.

Geoffrey Romer

unread,
May 19, 2014, 6:41:08 PM5/19/14
to std-pr...@isocpp.org, leszek....@gmail.com
I'm a fan of functional-style pattern matching, and frequently find myself wishing for it in C++, but there's certainly no "just" about it; it's not at all clear what pattern matching in C++ would even look like, much less what would constitute "the whole way" as opposed to part-way. What would be the syntax for patterns? How would the language select a matching pattern for a call, and populate the pattern variables? Could user-defined types participate in pattern-matching, other than as leaf variables? If so, how would the semantics be specified?

By the way, can you provide a link to the type-case patterns proposal you refer to?

Ville Voutilainen

unread,
May 19, 2014, 6:43:18 PM5/19/14
to std-pr...@isocpp.org
On 20 May 2014 01:41, 'Geoffrey Romer' via ISO C++ Standard - Future
Proposals > I'm a fan of functional-style pattern matching, and
frequently find myself
> wishing for it in C++, but there's certainly no "just" about it; it's not at
> all clear what pattern matching in C++ would even look like, much less what
> would constitute "the whole way" as opposed to part-way. What would be the
> syntax for patterns? How would the language select a matching pattern for a
> call, and populate the pattern variables? Could user-defined types
> participate in pattern-matching, other than as leaf variables? If so, how
> would the semantics be specified?
>
> By the way, can you provide a link to the type-case patterns proposal you
> refer to?

My _guess_ would be that it's this one:
http://open-std.org/JTC1/SC22/WG21/docs/papers/2012/n3449.pdf

This might also be of interest:
http://www.stroustrup.com/OpenPatternMatching.pdf

Douglas Boffey

unread,
May 21, 2014, 9:48:22 AM5/21/14
to std-pr...@isocpp.org
 
< A a, B b > = s;
 
Would you also allow 
 
  <A a, B b>=s;
 
(i.e. greater-or-equal instead of greater-than followed by equal-to)? 

leszek....@gmail.com

unread,
May 22, 2014, 1:12:36 PM5/22/14
to std-pr...@isocpp.org, leszek....@gmail.com
On Monday, 19 May 2014 23:41:08 UTC+1, Geoffrey Romer wrote:
I'm a fan of functional-style pattern matching, and frequently find myself wishing for it in C++, but there's certainly no "just" about it; it's not at all clear what pattern matching in C++ would even look like, much less what would constitute "the whole way" as opposed to part-way. What would be the syntax for patterns? How would the language select a matching pattern for a call, and populate the pattern variables? Could user-defined types participate in pattern-matching, other than as leaf variables? If so, how would the semantics be specified?

I agree that "just" is inappropriate here. In my mind, there are two elements to pattern matching; switching based on pattern, as in the type-casing example, and deconstructing based on pattern. Although these are both applications of pattern matching in a very general sense, I feel that they are almost entirely distinct from a semantic point of view: pattern-based switching is a form of multiple dispatch (something like "runtime overloading"), while pattern-based deconstruction is used for assignment to L-values.

Now, in a nice functional language like SML, we can conflate the two into very elegant syntax, mostly because all it really supports is primitive types, tuples and tagged unions (algebraic types). Then, the pattern-matching (both for switching and deconstructing) looks the same as constructing the type. However, once you start having more complex types, or rather, classes and objects, this starts to break down. This is why Python and Clojure have the two as separate features: deconstructing becomes tuple unpacking (Python) or destructing (Clojure), while switching becomes multimethods (as function decorators in Python or defmulti/defmethod in Clojure). Both of these happen to allow decontruction in their multimethods just by virtue of having deconstruction of any function parameters, but the two types of pattern-matching are still separate beasts.

So, with that in mind, I can't imagine what general pattern-matching would look like in C++; I can't even think of what general deconstructing pattern matching would look like; naturally, the constructor-like syntax that SML uses is not suitable in C++. My original proposal was based on Python/Clojure style tuple-only deconstructing pattern matching, because that's a) what I miss the most, and b) the best I could think of with minimal syntax/semantics changes to C++. 

leszek....@gmail.com

unread,
May 22, 2014, 1:23:32 PM5/22/14
to std-pr...@isocpp.org, mw_t...@users.sourceforge.net
On Monday, 19 May 2014 17:42:08 UTC+1, Matthew Woehlke wrote:
On 2014-02-27 19:33, leszek....@gmail.com wrote:
> On Thursday, 27 February 2014 18:52:20 UTC, Matthew Woehlke wrote:
>> should we also allow e.g.:
>>
>> (int a, double b) = foo();
>
> I was thinking this too, but I'm a little bit worried about having
> parenthesised expressions as L-values -- I'm not sure how pleased the
> parser people would be about something like this, especially since (a,b) is
> a perfectly valid L-value (and not a tuple unpacking).

Actually... is this even an issue? It seems that a declaration in this
context is currently invalid, ergo as soon as the compiler sees '('
followed by a typename (or at worst, further followed by an identifier),
it could "know" that it is dealing with a tuple unpacking.

Sure, but this starts being very context-dependent lookahead, I'm not sure how compiler-friendly that is.

Also, I'm not convinced that
    (auto a, auto b) = f();
is more readable than
    auto (a, b) = f();
although perhaps allowing
    (int a, double b) = f();
in preference to
    auto (int a, double b) = f();
is worth it. 

(As a bonus, we could also newly permit e.g. '(int a) = expr', where
expr could be a tuple (get<0>) or even an int literal.)

Thumbs up from me on allowing single element tuples in the syntax.

As a fallback, I'd be okay with Richard's suggestion of using <>'s
instead of ()'s.

I'm wary of <>'s simply because they look more like template definitions than something to do with assignment, plus Douglas's parsing ambiguity (let's not have the >> mess all over again). Though I don't have a strong opinion here.

Matthew Woehlke

unread,
May 22, 2014, 1:51:03 PM5/22/14
to std-pr...@isocpp.org
On 2014-05-22 13:23, leszek....@gmail.com wrote:
> On Monday, 19 May 2014 17:42:08 UTC+1, Matthew Woehlke wrote:
>>
>> On 2014-02-27 19:33, leszek....@gmail.com <javascript:> wrote:
>>> On Thursday, 27 February 2014 18:52:20 UTC, Matthew Woehlke wrote:
>>>> should we also allow e.g.:
>>>>
>>>> (int a, double b) = foo();
>>>
>>> I was thinking this too, but I'm a little bit worried about having
>>> parenthesised expressions as L-values -- I'm not sure how pleased the
>>> parser people would be about something like this, especially since (a,b)
>> is
>>> a perfectly valid L-value (and not a tuple unpacking).
>>
>> Actually... is this even an issue? It seems that a declaration in this
>> context is currently invalid, ergo as soon as the compiler sees '('
>> followed by a typename (or at worst, further followed by an identifier),
>> it could "know" that it is dealing with a tuple unpacking.
>>
>
> Sure, but this starts being very context-dependent lookahead, I'm not sure
> how compiler-friendly that is.

What's to look ahead? When you see a '(', set a 'might be tuple
unpacking' flag and get on with parsing. If you end up seeing what looks
like a declaration, check the flag. It can be done with state rather
than look-ahead (no more look-ahead than is needed for declarations
anyway, I think).

> Also, I'm not convinced that
> (auto a, auto b) = f();
> is more readable than
> auto (a, b) = f();

It isn't, but...

> although perhaps allowing
> (int a, double b) = f();

...I'd allow it for the case of non-matching explicit types (i.e. this).

> in preference to
> auto (int a, double b) = f();
> is worth it.

This makes no sense... 'auto int a = f()'? We're back to 'auto' being a
storage specifier and not a type. Or something...

>> As a fallback, I'd be okay with Richard's suggestion of using <>'s
>> instead of ()'s.
>
> I'm wary of <>'s simply because they look more like template definitions
> than something to do with assignment, plus Douglas's parsing ambiguity
> (let's not have the >> mess all over again). Though I don't have a strong
> opinion here.

Sure. I'd prefer ()'s also. Just that I'd rather have <>'s than not have
the feature at all :-).

--
Matthew

leszek....@gmail.com

unread,
May 28, 2014, 2:19:31 PM5/28/14
to std-pr...@isocpp.org, mw_t...@users.sourceforge.net
On Thursday, 22 May 2014 18:51:03 UTC+1, Matthew Woehlke wrote:
> Sure, but this starts being very context-dependent lookahead, I'm not sure
> how compiler-friendly that is.

What's to look ahead? When you see a '(', set a 'might be tuple
unpacking' flag and get on with parsing. If you end up seeing what looks
like a declaration, check the flag. It can be done with state rather
than look-ahead (no more look-ahead than is needed for declarations
anyway, I think).

Sounds plausible, I'm just being careful. Hence "not sure" rather than "it's not". I might post a message on the clang mailing list asking their opinion.
 

> Also, I'm not convinced that
>     (auto a, auto b) = f();
> is more readable than
>     auto (a, b) = f();

It isn't, but I'd allow it for the case of non-matching explicit types.

True, but I'm trying to optimise for the common case. The whole point here is to decrease syntax noise, and so I'd be tempted to keep `auto (a, b) = f()` and disallow explicit types entirely. My justification for this is that you'd only want explicit types if you:
  a) want to be explicit about return types, in which case you're better off being explicit about the fact the what is actually returned is a tuple (and you won't be happy about the direction that C++ is going in); or
  b) want to convert types, in which case you're better off doing this explicitly rather than implicitly:
    auto (a, b) = f(); 
    double b2 = b;   // or even   auto b2 = double(b);  if you're so inclined
 

Sure. I'd prefer ()'s also. Just that I'd rather have <>'s than not have
the feature at all :-).

Agreed, which is why I'm not expressing a strong opinion on this point!

Bengt Gustafsson

unread,
May 28, 2014, 6:16:26 PM5/28/14
to std-pr...@isocpp.org, mw_t...@users.sourceforge.net, leszek....@gmail.com
I think that what we are looking for here is something like "reverse" initializers. Currently we can construct a POD struct and more with a set of values in { } braces for instance:

struct Point {
  int x;
  int y;
};

Point p{1,2};

But we can't do the reverse:

{int x, int y} = p;

If we could do this, the function return case follows immediately (e.g. in this case for a function returning a Point, or anything else where the two first "members" can be converted to int).

However, there are a few issues with the above syntax, some of which have been pointed out, such as parsing and the uncertainty of the scope of the x and y. A third issue I haven't seen discussed is the case of
a mix of declarations and preexisting variables on the left of =.

To resolve this it would be possible to use this syntax:

inline { int x, int y} = p;

Here the word inline is used to indicate that the scope of x and y is extended beyond their own little useless {} pair. It also clarifies to the parser that this is a unpacking/destructing/whatever construct without undue look ahead. Finally it would be perfectly possible to allow any mix of preexisting and new variables and auto:

int y;
inline { auto x, y } = p;

I used comma separation above to make it syntactically closer to an initializer but maybe that was not so wise as if you look at the last example it seems that we have two auto variables x, y while my intention was that y was the previously declared variable. On the other hand, if semicolon was used it would not be consistent with an initializer. The idea with associating it with an initializer is that we could use the same rule set when it comes to how lhs and rhs elements match up and how nesting could be allowed.

I think this can be elaborated further to reach some kind of common ground. And maybe we would do better disregarding parsing problems and get rid of the 'inline' or auto and demand that parsing a {} with a trailing = be treated as an unpacking clause. That should not be so hard to do in practice. Much easier than passing the body of an inline function in the class head, that's for sure!

gad...@gmail.com

unread,
May 30, 2014, 12:46:36 PM5/30/14
to std-pr...@isocpp.org
I think that what we are looking for here is something like "reverse" initializers.

This made me remember Scala and its unapply() methods. See  http://lampwww.epfl.ch/~emir/written/MatchingObjectsWithPatterns-TR.pdf  section 4 Extractors if you are curious.

walter1234

unread,
Jun 15, 2014, 8:07:49 AM6/15/14
to std-pr...@isocpp.org, leszek....@gmail.com
Something related, would it be possible to retrofit swifts .0 , .1 .. syntax for tuple member access into C++?
I think I'd rather have that than destructuring sugar.

perhaps those could be synonymous for accessing struct elements by index. 
c++ does know the order of struct elements, right, since you can use the order with an initialiser list.

(on another note, i'm very surprised the std lib doesn't use a  structKeyValue{.. key, .. value} in various contexts with std::map , that would be clearer than using tuples in that instance)


leszek....@gmail.com

unread,
Jul 28, 2014, 5:37:57 AM7/28/14
to std-pr...@isocpp.org, leszek....@gmail.com
Something related, would it be possible to retrofit swifts .0 , .1 .. syntax for tuple member access into C++?

I really like that idea, especially since get<0>(x) is so verbose...
 
I think I'd rather have that than destructuring sugar.

... but I'd rather have it "in addition to" destructuring sugar.


inline { int x, int y} = p;

This is elegant in both reuse of keywords, and this idea of reverse initializers, but my issue with it is the increased verbosity; after all, the whole point of this proposal is to have a short and handy sugar syntax for getting at the unknown-type (or rather "I-don't-care"-type) elements of a tuple.

A third issue I haven't seen discussed is the case of a mix of declarations and preexisting variables on the left of =.

I don't think this is a problem. Since the destructuring is just sugar, I think it doesn't need to handle more complex cases such as this one; for those, there's always manual unpacking with get<n>() or std::tie.

David Krauss

unread,
Jul 28, 2014, 7:01:46 AM7/28/14
to std-pr...@isocpp.org
On 2014–06–15, at 8:07 PM, walter1234 <walt...@gmail.com> wrote:

Something related, would it be possible to retrofit swifts .0 , .1 .. syntax for tuple member access into C++?

Or, a way to convert a constant expression argument to a std::integral_constant type. That would allow a subscript operator template to be defined, such that tuple[ 0 ] could have a different type from tuple[ 1 ]. It would also provide a generally convenient gateway between constexpr and template metaprogramming.

inkwizyt...@gmail.com

unread,
Jul 28, 2014, 2:00:53 PM7/28/14
to std-pr...@isocpp.org
user literals could be help there:

template<int i>
class S{
};

template<char... K>
 
constexpr S<string_to_int<K...>::value> operator"" _f()
{
 
return {};
}

struct Tuple : public std::tuple<char, int, std::string>
{
 
template<int i>
 
auto operator[](S<i>) -> decltype(auto)
 
{
   
return (std::get<i>(*this));
 
}
};

int main()
{
 
Tuple a;
  a
[0_f] = 5;
  std
::cout << a[0_f] << std::endl;
}

Bengt Gustafsson

unread,
Jul 29, 2014, 4:58:02 AM7/29/14
to std-pr...@isocpp.org, leszek....@gmail.com
I wasn't thinking of my idea

inline { x, int y } = func();


as something specific to the case that func() returns a tuple, that would be a terrible wart connecting a core language feature to a std class. Instead I thought of it as a general reversal of the {} initializer i.e. to pick up the members of whatever object/tuple/struct that func returns().

Thus tie() or get<> does not solve the general case that this feature does.

walter1234

unread,
Jul 30, 2014, 10:29:34 AM7/30/14
to std-pr...@isocpp.org, leszek....@gmail.com
Would `struct` in an lvalue location work , directly analogous to the destructuring in rust, just with sequential access allowed to named structs (so they can be used like tuples in functional languages)

eg 

   struct  { auto x,auto y,auto z } = std::make_tuple(1,2.0,"three");  //x=1, y=2.0, z="three"


Such a shame how simple brackets and commas already parse in C++.. the elegance of let (x,y)=... in rust is a big draw.


Tony V E

unread,
Jul 30, 2014, 12:30:20 PM7/30/14
to std-pr...@isocpp.org
We could just go all out and invent new brackets:  :{ x, y, z }: or {: :} or {| |} or ...
(I suspect {| is currently always an error, whereas {: could be the start of something like {::x, and (not sure) maybe :{ could be part of a conditional-operator b ? x :{y,z}, although I don't think that is valid... yet.)

Tony

Ville Voutilainen

unread,
Jul 30, 2014, 1:11:54 PM7/30/14
to std-pr...@isocpp.org
I have serious trouble seeing sufficient motivation to invent new syntax to
support more convenient ways to unpack ad-hoc types like tuples. Just
because Rust does such things (as does Python) is not a sufficient
reason.

Tony V E

unread,
Jul 30, 2014, 1:39:11 PM7/30/14
to std-pr...@isocpp.org
I'd like better support for tuples, but I agree new brackets is going to far, even though I suggested it. Unless the new brackets had useful meaning beyond tuples.
(I often suggest things that I might not agree with, or at least haven't decided on yet, in case others like it or it leads to something even better.)

Tony

Edward Catmur

unread,
Jul 30, 2014, 7:10:26 PM7/30/14
to std-pr...@isocpp.org
I'm sure other people have already mentioned it, but I'd like to reiterate that it is already entirely possible to unpack tuples and similar using apply (n3915), at the cost of an extra level of nesting:

auto t = std::make_tuple("hello", 123.45, 66);
apply
([&](auto s, auto d, auto i){
  std
::cout << s << d << i;
}, t);

Iterating over an associative container:

std::map<int, std::string> m;
for (auto&& kv: m) {
  apply
([&](auto const&& k, auto&& v) {
   
// ...
 
}, kv);
}

Any new syntax would have to represent a significant improvement over this, which seems a pretty tough bar to attain.

It would probably read better with the arguments in the opposite order, but it's trivial to write the equivalent of unpack = flip apply. One can even write an "unpacking operator" using everyone's favorite overloadable binary operator:

template<typename T, typename F> decltype(auto) operator->*(T&& t, F&& f) {
 
return std::apply(std::forward<F>(f), std::forward<T>(t));
}

auto t = std::make_tuple("hello", 123.45, 66);
t
->* [&](auto s, auto d, auto i) {  // t goes to s, d, and i
  std
::cout << s << d << i;
};

(Heh.)

David Krauss

unread,
Jul 30, 2014, 7:22:36 PM7/30/14
to std-pr...@isocpp.org
On 2014–07–30, at 10:29 PM, walter1234 <walt...@gmail.com> wrote:

Would `struct` in an lvalue location work , directly analogous to the destructuring in rust, just with sequential access allowed to named structs (so they can be used like tuples in functional languages)

eg 

   struct  { auto x,auto y,auto z } = std::make_tuple(1,2.0,"three");  //x=1, y=2.0, z=“three"

Note, this is similar to a C11 anonymous structure in syntax and semantics. The unpacked x, y, and z wouldn’t need to be a new kind of entity. The “damage” would be contained to initialization semantics, and there might be synergy with deduced class template arguments as well.

inkwizyt...@gmail.com

unread,
Aug 1, 2014, 4:30:38 PM8/1/14
to std-pr...@isocpp.org, leszek....@gmail.com
If you skip `auto` you can do somthing like this now:

template<typename Tuple>
struct cast_t
{
 
Tuple t;
 
template<typename T>
 
operator T()
 
{
   
return T{ std::get<0>(t), std::get<1>(t) /*... ect*/ };
 
}
};

template<typename Tuple>
cast_t
<Tuple> cast(const Tuple& t)
{
 
return cast_t<Tuple>{ t };
}


int main()
{
 
struct { int a; int b; } z = cast(std::make_tuple(1, 2));
  std
::cout << z.a << " " << z.b << std::endl;
}

Reply all
Reply to author
Forward
Message has been deleted
0 new messages