c++for-looplanguage-lawyerstd-pairstd-tie

Using std::tie as a range for loop target


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.


Solution

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