В чем преимущество использования ссылок пересылки в циклах for на основе диапазона?

115

const auto&было бы достаточно, если я хочу выполнять операции только для чтения. Однако я наткнулся на

for (auto&& e : v)  // v is non-const

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

Возможно ли, что в некоторых непонятных угловых случаях есть некоторое преимущество в производительности при использовании ссылок пересылки по сравнению с auto&или const auto&?

( shared_ptrподозревается в неясных делах)


Обновление Два примера, которые я нашел в своих избранных:

Есть ли недостаток использования константной ссылки при итерации по базовым типам?
Могу ли я легко перебирать значения карты, используя цикл for на основе диапазона?

Пожалуйста, сконцентрируйтесь на вопросе: зачем мне использовать auto && в циклах на основе диапазона?

Али
источник
4
Вы действительно видите это «часто»?
Гонки легкости на орбите,
2
Я не уверен, что в вашем вопросе достаточно контекста, чтобы оценить, насколько он «сумасшедший» там, где вы его видите.
Гонки за легкостью на орбите,
2
@LightnessRacesinOrbit Короче говоря: зачем мне использовать auto&&циклы for на основе диапазона?
Али

Ответы:

94

Единственное преимущество, которое я вижу, - это когда итератор последовательности возвращает ссылку на прокси, и вам нужно работать с этой ссылкой неконстантным способом. Например, рассмотрим:

#include <vector>

int main()
{
    std::vector<bool> v(10);
    for (auto& e : v)
        e = true;
}

Это не компилируется, потому что rvalue, vector<bool>::referenceвозвращаемое из iterator, не будет привязываться к неконстантной ссылке lvalue. Но это сработает:

#include <vector>

int main()
{
    std::vector<bool> v(10);
    for (auto&& e : v)
        e = true;
}

При этом я бы не стал кодировать таким образом, если бы вы не знали, что вам нужно удовлетворить такой вариант использования. То есть я бы не стал делать это бесплатно, потому что это заставляет людей задуматься, чем вы занимаетесь . И если бы я это сделал, не помешало бы добавить комментарий, почему:

#include <vector>

int main()
{
    std::vector<bool> v(10);
    // using auto&& so that I can handle the rvalue reference
    //   returned for the vector<bool> case
    for (auto&& e : v)
        e = true;
}

редактировать

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

Говард Хиннант
источник
3
С другой стороны, явного недостатка нет? (Помимо потенциально сбивающих с толку людей, о которых я не думаю, что стоит особо упоминать об этом.)
ildjarn
7
Еще один термин для написания кода, который излишне сбивает людей с толку: написание запутанного кода. Лучше всего сделать ваш код максимально простым, но не проще. Это поможет вести обратный отсчет ошибок. При этом, по мере того, как && становится более привычным, возможно, через 5 лет люди будут ожидать идиомы auto && (при условии, что она на самом деле не причиняет вреда). Не знаю, случится это или нет. Но простота в глазах смотрящего, и если вы пишете не только для себя, примите во внимание своих читателей.
Ховард Хиннант,
7
Я предпочитаю, const auto&когда хочу, чтобы компилятор помог мне проверить, случайно ли я не изменил элементы в последовательности.
Ховард Хиннант,
31
Мне лично нравится использовать auto&&общий код, когда мне нужно изменить элементы последовательности. Если нет, я просто буду придерживаться auto const&.
Xeo
7
@Xeo: +1 Именно благодаря таким энтузиастам, как вы, постоянно экспериментирующим и стремящимся к лучшему, C ++ продолжает развиваться. Спасибо. :-)
Ховард Хиннант
26

Использование auto&&или универсальные ссылки с диапазоном на основе for-loop имеет то преимущество , что вы улавливает то , что вы получите. Для большинства типов итераторов вы, вероятно, получите либо a, T&либо a T const&для некоторого типа T. Интересен случай, когда разыменование итератора приводит к временному результату: C ++ 2011 имеет ослабленные требования, и итераторы не обязательно требуются для получения lvalue. Использование универсальных ссылок соответствует пересылке аргументов в std::for_each():

template <typename InIt, typename F>
F std::for_each(InIt it, InIt end, F f) {
    for (; it != end; ++it) {
        f(*it); // <---------------------- here
    }
    return f;
}

Объект функции fможет лечить T&, T const&и по- Tдругому. Почему тело цикла на основе forдиапазона должно отличаться? Конечно, чтобы на самом деле воспользоваться выводом типа с помощью универсальных ссылок, вам нужно будет передать их соответственно:

for (auto&& x: range) {
    f(std::forward<decltype(x)>(x));
}

Конечно, использование std::forward()означает, что вы принимаете любые возвращаемые значения для перемещения. Имеют ли такие объекты смысл в коде, отличном от шаблона, я не знаю (пока?). Я могу себе представить, что использование универсальных ссылок может дать компилятору больше информации, чтобы сделать правильный выбор. В шаблонном коде он не принимает никакого решения о том, что должно происходить с объектами.

Дитмар Кюль
источник
9

Практически всегда пользуюсь auto&&. Зачем вас укусил крайний случай, когда в этом нет необходимости? Он тоже короче, и я просто считаю его более ... прозрачным. Когда вы используете auto&& x, то знаете, что xэто точно *it, каждый раз.

щенок
источник
29
Моя проблема заключается в том , что ваш отказывается const-ness с , auto&&если const auto&хватает. Вопрос касается угловых случаев, когда меня могут укусить. Какие крайние случаи еще не упомянули Дитмар или Ховард?
Али
2
Вот способ получить укус, если вы его употребляете auto&&. Если тип, который вы захватываете, следует переместить в тело цикла (например) и изменить на более позднюю дату, чтобы он разрешился в const&тип, ваш код будет молча продолжать работу, но ваши ходы будут копиями. Этот код будет очень обманчивым. Если, однако, вы явно укажете тип как ссылку на r-значение, тот, кто изменил тип контейнера, получит ошибку компиляции, потому что вы действительно хотели, чтобы эти объекты перемещались, а не копировались ...
cyberbisson
@cyberbisson Я только что наткнулся на это: как я могу применить ссылку rvalue без явного указания типа? Что-то вроде for (std::decay_t<decltype(*v.begin())>&& e: v)? Думаю, есть способ получше ...
Джерри Ма
@JerryMa Это зависит от того, что вы действительно знаете о типе. Как упоминалось выше, auto&&это даст «универсальную ссылку», так что все, кроме этого, даст вам более конкретный тип. Если vпредполагается, что это a vector, вы можете сделать это decltype(v)::value_type&&, как я полагаю, вы хотели, взяв результат operator*на типе итератора. Вы также decltype(begin(v))::value_type&&можете проверить типы итератора вместо контейнера. Если мы так мало разбираемся в типе, мы могли бы подумать, что будет немного понятнее просто пойти с auto&&...
cyberbisson