Читать «Эффективный и современный С++. 42 рекомендации по использованию С++11 и С++14» онлайн - страница 34

Скотт Мейерс

Следует запомнить

• Переменные, объявленные как auto, должны быть инициализированы; в общем случае они невосприимчивы к несоответствиям типов, которые могут привести к проблемам переносимости или эффективности; могут облегчить процесс рефакторинга; и обычно требуют куда меньшего количества ударов по клавишам, чем переменные с явно указанными типами.

• Переменные, объявленные как auto, могут быть подвержены неприятностям, описанным в разделах 1.2 и 2.2.

2.2. Если auto выводит нежелательный тип, используйте явно типизированный инициализатор

В разделе 2.1 поясняется, что применение auto для объявления переменных предоставляет ряд технических преимуществ по сравнению с явным указанием типов, но иногда вывод типа auto идет налево там, где вы хотите направо. Предположим, например, что у меня есть функция, которая получает Widget и возвращает std::vector<bool>, где каждый bool указывает, обладает ли Widget определенным свойством:

std::vector<bool> features(const Widget& w);

Предположим далее, что пятый бит указывает наличие высокого приоритета у Widget. Мы можем написать следующий код.

Widget w;

bool highPriority = features(w)[5]; // Имеет ли w высокий

                                    // приоритет?

processWidget(w, highPriority);     // Обработка w в соответ-

                                    // ствии с приоритетом

В этом коде нет ничего неверного. Он корректно работает. Но если мы внесем кажущееся безобидным изменение и заменим явный тип highPriority типом auto

auto highPriority = features(w)[5]; // Имеет ли w высокий

                                    // приоритет?

то ситуация изменится. Код будет продолжать компилироваться, но его поведение больше не будет предсказуемым:

processWidget(w, highPriority); // Неопределенное поведение!

Как указано в комментарии, вызов processWidget теперь имеет неопределенное поведение. Но почему? Ответ, скорее всего, вас удивит. В коде, использующем auto, тип highPriority больше не является bool. Хотя концептуально std::vector<bool> хранит значения bool, operator[] у std::vector<bool> не возвращает ссылку на элемент контейнера (то, что std::vector::operator[] возвращает для всех типов за исключением bool). Вместо этого возвращается объект типа std::vector<bool>::reference (класса, вложенного в std::vector<bool>).

Тип std::vector<bool>::reference существует потому, что std::vector<bool> определен как хранящий значения bool в упакованном виде, по одному биту на каждое значение. Это создает проблему для оператора operator[] класса std::vector<bool>, поскольку operator[] класса std: :vector<T> должен возвращать T&, но С++ запрещает ссылаться на отдельные биты. Будучи не в состоянии вернуть bool&, operator[] класса std::vector<bool> возвращает объект, который действует подобно bool&. Для успешной работы объекты std::vector<bool>::reference должны быть применимы по сути во всех контекстах, где применим bool&. Среди прочих возможностей std::vector<bool>::reference обладает неявным преобразованием в bool. (Не в bool&, а именно в bool. Пояснение всего набора методов, используемых std::vector<bool>::reference для эмуляции поведения bool&, завело бы нас слишком далеко, так что я просто замечу, что это неявное преобразование является только одним из камней в существенно большей мозаике.)