Яка перевага використання посилань для переадресації в діапазоні для циклів?


115

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

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

пару разів останнім часом. Це мене здивує:

Можливо чи, що в деяких неясних випадках кутових є деякі бенефіси в використанні посилання переадресації, по порівнянні з auto&або const auto&?

( shared_ptrє підозрюваним у незрозумілих кутових справах)


Оновити два приклади, які я знайшов у своїх улюблених:

Якийсь недолік використання const reference при ітерації над базовими типами?
Чи можу я легко перебирати значення карти за допомогою діапазону для циклу?

Будь ласка, зосередьтесь на питанні: чому я хочу використовувати авто && в діапазоні для циклів?


4
Ви дійсно бачите це "часто"?
Гонки легкості по орбіті

2
Я не впевнений, що у вашому запитанні достатньо контексту для мене, щоб оцінити, наскільки "божевільний" це те, де ви його бачите.
Гонки легкості по орбіті

2
@LightnessRacesinOrbit Довга історія: чому я хочу використовувати auto&&в діапазоні для циклів?
Алі

Відповіді:


94

Єдиною перевагою, яку я бачу, є те, коли ітератор послідовності повертає проксі-посилання, і вам потрібно працювати над цим посиланням не const способом. Наприклад, розглянемо:

#include <vector>

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

Це не компілюється, тому що rvalue, vector<bool>::referenceповернута з iteratorне буде прив'язувати до не-const посилання 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 років люди прийдуть очікувати авто && ідіоми (припускаючи, що це насправді не шкодить). Я не знаю, чи це станеться чи ні. Але просте в очах глядача, і якщо ви пишете більше, ніж про себе, врахуйте своїх читачів.
Говард Хінант

7
Я вважаю const auto&за краще, коли компілятор хочу допомогти мені перевірити, чи не випадково я змінив елементи в послідовності.
Говард Хіннант

31
Мені особисто подобається використовувати auto&&в загальному коді, де мені потрібно змінити елементи послідовності. Якщо я цього не зроблю, я просто дотримуюся auto const&.
Xeo

7
@Xeo: +1 Через ентузіастів, як ти, що постійно експериментують та прагнуть до кращих способів робити, C ++ продовжує розвиватися. Дякую. :-)
Говард Хіннант

26

Використання auto&&або універсальні посилання на основі діапазону for-loop мають перевагу в тому, що ви фіксуєте отримане. Для більшості видів ітераторів ви, мабуть, отримаєте або a, T&або aT const& для певного типу T. Цікавий випадок, коли перенаправлення ітератора дає тимчасовий характер: C ++ 2011 отримав невимушені вимоги, ітератори не обов'язково вимагають значення. Використання універсальних посилань відповідає аргументу, що пересилає 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здаєшся, auto&&якщо const auto&достатньо. Питання задає кутові випадки, коли мене можна покусати. Які кутові випадки ще не були згадані Дітмаром чи Говардом?
Алі

2
Ось спосіб отримати вкусив , якщо ви робите використання auto&&. Якщо тип, який ви захоплюєте, слід перемістити в тілі циклу (наприклад) і змінити на більш пізню дату, щоб він зміг на const&тип, ваш код буде мовчки продовжувати функціонувати, але ваші рухи будуть копіями. Цей код буде дуже оманливим. Якщо ви чітко вкажете тип як посилання на значення r, той, хто змінив тип контейнера, отримає помилку компіляції, оскільки ви дійсно хотіли, щоб ці об’єкти були переміщені, а не скопійовані ...
cyberbisson

@cyberbisson Я щойно натрапив на це: як я можу застосувати посилання на оцінку без чітко вказаного типу? Щось на кшталт for (std::decay_t<decltype(*v.begin())>&& e: v)? Я думаю, є кращий спосіб ...
Джеррі Ма

@JerryMa Це залежить від того, що ви дійсно знаєте про тип. Як було сказано вище, auto&&дасть "універсальну орієнтир", тому будь-що, крім цього, отримає вам більш конкретний тип. Якщо vпередбачається vector, що ви є a , ви могли б зробити decltype(v)::value_type&&, що я вважаю, що ви хотіли, взявши результат operator*за типом ітератора. Ви також decltype(begin(v))::value_type&&можете перевірити типи ітератора замість контейнера. Якщо ми маємо так мало розуміння типу, хоча, ми можемо вважати, що трохи зрозуміліше просто піти з auto&&...
cyberbisson
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.