Qual é a vantagem de usar referências de encaminhamento em loops for baseados em intervalo?

115

const auto&seria suficiente se eu quiser realizar operações somente leitura. No entanto, eu esbarrei em

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

algumas vezes recentemente. Isso me faz pensar:

É possível que, em alguns casos obscuros, haja algum benefício de desempenho no uso de referências de encaminhamento, em comparação com auto&ou const auto&?

( shared_ptré suspeito de casos obscuros)


Atualizar dois exemplos que encontrei em meus favoritos:

Alguma desvantagem de usar referência const ao iterar sobre tipos básicos?
Posso iterar facilmente os valores de um mapa usando um loop for baseado em intervalo?

Concentre-se na pergunta: por que eu deveria usar auto && em loops for baseados em faixa?

Todos
fonte

Respostas:

94

A única vantagem que vejo é quando o iterador de sequência retorna uma referência de proxy e você precisa operar nessa referência de uma maneira não constante. Por exemplo, considere:

#include <vector>

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

Isso não compila porque rvalue vector<bool>::referenceretornado de iteratornão vinculará a uma referência de lvalue não constante. Mas isso vai funcionar:

#include <vector>

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

Dito isso, eu não codificaria dessa forma, a menos que você soubesse que precisava satisfazer esse caso de uso. Ou seja, eu não faria isso gratuitamente porque faz com que as pessoas se perguntem o que você está fazendo. E se eu fizesse isso, não faria mal incluir um comentário sobre o motivo:

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

Editar

Este meu último caso deveria realmente ser um modelo para fazer sentido. Se você souber que o loop está sempre manipulando uma referência de proxy, autotambém funcionaria auto&&. Mas quando o loop às vezes estava lidando com referências não-proxy e às vezes com referências-proxy, acho que auto&&seria a solução de escolha.

Howard Hinnant
fonte
26

O uso de referências universaisauto&& ou com um loop baseado em intervalo tem a vantagem de capturar o que obtém. Para a maioria dos tipos de iteradores, você provavelmente obterá a ou a para algum tipo . O caso interessante é quando a desreferenciação de um iterador resulta em um temporário: C ++ 2011 tem requisitos relaxados e os iteradores não são necessariamente necessários para produzir um lvalue. O uso de referências universais corresponde ao encaminhamento de argumento em :forT&T const&Tstd::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;
}

O objeto de função fpode tratar T&, T const&e Tde forma diferente. Por que o corpo de um forloop baseado em alcance deve ser diferente? Claro, para realmente tirar vantagem de ter deduzido o tipo usando referências universais, você precisa transmiti-los de forma correspondente:

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

Obviamente, usar std::forward()significa que você aceita qualquer valor retornado para ser movido. Se objetos como esse fazem muito sentido em código não-template, eu não sei (ainda?). Posso imaginar que usar referências universais pode oferecer mais informações para o compilador fazer a coisa certa. No código modelado, ele fica fora de qualquer decisão sobre o que deve acontecer com os objetos.

Dietmar Kühl
fonte
9

Eu quase sempre uso auto&&. Por que ser mordido por um caso extremo quando você não precisa? É mais curto para digitar também, e simplesmente acho mais ... transparente. Quando você usa auto&& x, sabe que xé exatamente *it, todas as vezes.

Cachorro
fonte