Использование std:: tie в качестве диапазона для цели цикла

Я хочу сделать что-то вроде следующего:

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

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

Однако это недопустимый код, потому что, как говорит стандарт, диапазон, основанный на цикле, определяется как эквивалент:

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

Если декларация для диапазона определена как:

для диапазона декларирования: attribute-specifier-seq_ {opt} decl-specifier-seq declarator

Итак, что удерживает меня в том, что decl-specifier-seq не помечен как необязательный?

Поэтому кажется, что я должен вернуться к старому стилю для циклов для этого 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;
  }
}

Но это кажется немного беспорядочным синтаксически для того, что кажется интуитивно более распространенной задачей, распаковывая результат вызова функции, который действителен во многих других контекстах.

Есть ли предложение добавить что-то к этому языку? Это даже разумная идея? Есть ли лучший способ сделать это, что я не замечаю? Я неправильно понимаю стандарт?

Очевидно, я мог бы создать свою собственную функцию для этого, но это тоже немного бесполезно для использования.

Ответы

Ответ 1

Вы все еще можете использовать range-for!

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

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

Или const auto& p, если вам не нужно/нужно изменить p.

ОБНОВЛЕНИЕ: С помощью вышеизложенного вы также можете переместить элементы связанным переменным с помощью std::move

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

для которого ваш предложенный синтаксис может плохо работать. Надуманный пример:

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

При этом у вас нет возможности переместить значения элементов в привязанные переменные, так как begin(), end() и т.д. из контейнера r-value не будут создавать итераторы перемещения. (Ну, да, вы можете адаптировать контейнер к чему-то, что возвращает итераторы, но это будет совершенно новая история)

Ответ 2

По состоянию на 03-21-2017, структурированные привязки являются частью С++.

Это позволяет прямо делать следующее:

//std::vector<std::pair<TypeA, TypeB>> someInitializingFunction();
for (auto [a, b] : someInitializingFunction()) {
  // do stuff;
}