Using std::tie as a range for loop target
Asked Answered
T

2

28

I want to do something like the following:

//std::vector<std::pair<TypeA, TypeB>> someInitializingFunction();
{
  TypeA a;
  TypeB b;

  for (std::tie(a, b) : someInitializingFunction()) {
    // do stuff;
  }
}

However, this is not valid code because, as the standard says, a range based for loop is defined as equivalent to:

{
  auto && __range = range-init;
  for ( auto __begin = begin-expr,
      __end = end-expr;
      __begin != __end;
      ++__begin ) {
    for-range-declaration = *__begin;
    statement
  }
}

Where a for-range-declaration is defined as:

for-range-declaration: attribute-specifier-seq_{opt} decl-specifier-seq declarator

So what's holding me back is that decl-specifier-seq is not marked as optional?

Therefore, it seems that I must fall back on old style for loops for this one a la:

std::vector<std::pair<TypeA, TypeB>> myList = someInitializingFunction();

{
  TypeA a;
  TypeB b;

  for (auto it = myList.begin(); it != myList.end(); ++it) {
    std::tie(a, b) = *it;
    // do stuff;
  }
}

But it seems a bit messy syntactically for what seems to intuitively be a rather common task, unpacking the result of a function call, which is valid in many other contexts.

Is there a proposal to add something this to the language? Is this even reasonable idea? Is there a better way to do this that I'm overlooking? Am I misreading the standard?

Obviously, I could put together my own function to do this, but that's also a bit messy to use as well.

Telekinesis answered 23/1, 2014 at 5:50 Comment(1)
Update: It will probably be possible to do that in C++17 with a very intuitive syntax. They are currently working at "structured bindings". It will then be possible to do: for (auto {a,b} : myList).Antitragus
L
19

You can still use range-for!

//std::vector<std::pair<TypeA, TypeB>> someInitializingFunction();
{
  TypeA a;
  TypeB b;

  for (auto& p : someInitializingFunction()) {
    std::tie(a, b) = p;
    // do stuff;
  }
}

Or const auto& p if you don't need/want to modify p.

UPDATE: With the above, you can also move the elements to the tied variables using std::move

for (auto& p : someInitializingFunction()) {
  std::tie(a, b) = std::move(p);
  // do stuff;
}

for which your proposed syntax may not handle well. A contrived example:

for (std::tie(a, b) : std::move(someInitializingFunction())) {}
// Note: std::move here is superfluous, as it's already an r-value
//    (may also hinder some optimizations). Purely for demonstration purposes.

With that, you don't have the ability to move the values of the elements to the tied variables, as begin(), end(), etc. from an r-value container won't produce move iterators. (Well, yes you could adapt the container into something that returns move iterators, but that would be a whole new story)

Leftward answered 23/1, 2014 at 5:54 Comment(1)
What if TypeA and TypeB are not default-constructible?Amalamalbena
T
21

As of 03-21-2017, Structured Bindings are part of C++.

This allows directly doing the following:

//std::vector<std::pair<TypeA, TypeB>> someInitializingFunction();
for (auto [a, b] : someInitializingFunction()) {
  // do stuff;
}
Telekinesis answered 23/3, 2017 at 21:53 Comment(2)
"are part of C++" well c++ standard is not contiuously evolving. It's released in versions. So, I guess as of this date this is destined to be part for the next c++ standard -- c++17 I guess. I am nitpicking this, because sadly I won't get backward support fro c++11. Anyways +1 for keeping the answer/question up to date.Apodictic
In the C++17 or beyond, this is absolutely the right answer—structured bindings are much more readable than std::tie().Nodule
L
19

You can still use range-for!

//std::vector<std::pair<TypeA, TypeB>> someInitializingFunction();
{
  TypeA a;
  TypeB b;

  for (auto& p : someInitializingFunction()) {
    std::tie(a, b) = p;
    // do stuff;
  }
}

Or const auto& p if you don't need/want to modify p.

UPDATE: With the above, you can also move the elements to the tied variables using std::move

for (auto& p : someInitializingFunction()) {
  std::tie(a, b) = std::move(p);
  // do stuff;
}

for which your proposed syntax may not handle well. A contrived example:

for (std::tie(a, b) : std::move(someInitializingFunction())) {}
// Note: std::move here is superfluous, as it's already an r-value
//    (may also hinder some optimizations). Purely for demonstration purposes.

With that, you don't have the ability to move the values of the elements to the tied variables, as begin(), end(), etc. from an r-value container won't produce move iterators. (Well, yes you could adapt the container into something that returns move iterators, but that would be a whole new story)

Leftward answered 23/1, 2014 at 5:54 Comment(1)
What if TypeA and TypeB are not default-constructible?Amalamalbena

© 2022 - 2024 — McMap. All rights reserved.