Подтвердить что ты не робот

Правильный стиль декларации в диапазоне для

Этот вопрос упомянул о явном, идиоматическом использовании С++ 11, основанного на диапазоне.

for (auto& elem: container) {
  // do something with elem
}

У меня были сомнения в том, какую ссылку вы должны использовать. Итераторы ввода могут возвращать значения r. Хотя неявный тип, введенный auto, можно вывести на const, который будет привязан к r-значению, который, похоже, не возникает.

Лучше ли вообще использовать идеальную пересылку?

for (auto && elem: container) {
  // do something with elem
}

Здесь я не вижу недостатков, но это выглядит слишком мило. Возможно, я все еще просто не написал достаточно С++ 11.

4b9b3361

Ответ 1

Во-первых, некоторые общие рекомендации о том, как использовать auto, который не является специфичным для range-for. auto&& может быть проблематичным, если инициализатор представляет собой значение x, относящееся к временному, поскольку расширение жизни в этом случае не может быть применено. Проще говоря, и с кодом:

// Pass-through identity function that doesn't construct objects
template<typename T>
T&&
id(T&& t)
{ return std::forward<T>(t); }

// Ok, lifetime extended
// T {} is a prvalue
auto&& i = T {};

T* address = &i;

// Still ok: lifetime of the object referred to by i exceed that of j
// id(whatever) is an xvalue
auto&& j = id(std::move(i));

// No other object is involved or were constructed,
// all those references are bound to the same object
assert( &j == address );

// Oops, temporary expires at semi-colon
// id(whatever) is an xvalue, again
auto&& k = id(T {});

Большая подсказка, что здесь что-то теневое, заключается в том, что id имеет тип возврата T&&. Если он вернул T, тогда id(whatever) будет значением prvalue, а возвращаемое временное время будет расширенным (хотя это будет связано с конструкцией).


С учетом этого, когда дело доходит до диапазона, хотя вы должны помнить, что for(auto&& ref: init) { /* body */ } указано примерно эквивалентно следующему (игнорируя некоторые детали, которые здесь не имеют значения):

{
    using std::begin;
    using std::end;
    auto&& range = init;
    for(auto b = begin(range), e = end(range); b != e; ++b) {
        auto&& ref = *b;
        /* body */
    }
}

Теперь нам нужно спросить себя, что, если *b - это xvalue (т.е. тип итератора имеет operator* возвращающий value_type&&, как в случае, например, с std::move_iterator<Iterator>)? Затем он должен ссылаться на объект, который переживет ref, так как строка auto&& ref = *b; не содержит временных. Следовательно, это безопасно. В противном случае, если *b является prvalue (т.е. Тип итератора имеет operator*, возвращающий T для некоторого типа объекта T), тогда время жизни временного расширения распространяется на остальную часть тела цикла. Во всех случаях вы в безопасности (случай, когда *b является lvalue, оставленным в качестве упражнения для читателя).

Я лично сильно использую auto&&, с диапазоном или без него. Но я спрашиваю себя каждый раз, является ли инициализатор значением xvalue или нет, и если да, то каково время жизни того, на что ссылается.