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

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

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

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

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

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


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

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

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

Ответ 1

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

#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;
}

Edit

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

Ответ 2

Использование auto&& или универсальных ссылок с диапазоном for -loop имеет то преимущество, что вы фиксируете то, что получаете. Для большинства типов итераторов вы, вероятно, получите либо T&, либо T const& для некоторого типа T. Интересным случаем является то, что разыменование итератора приводит к временному: С++ 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 -loop отличалось? Конечно, чтобы на самом деле воспользоваться выводом типа с использованием универсальных ссылок, вам нужно было бы их соответствующим образом передать:

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

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

Ответ 3

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