Основанное на диапазоне выражение for (C++)
Циклически и последовательно выполняет оператор ( statement ) каждого элемента в выражении ( expression ).
Синтаксис
for ( Выражение объявления : диапазона )
инструкция
Замечания
Используйте инструкцию на основе for диапазона для создания циклов, которые должны выполняться через диапазон, который определяется как все, что можно итерировать, например, std::vector или любую другую последовательность стандартной библиотеки C++, диапазон которой определяется begin() и end() . Имя, объявленное в for-range-declaration части, является локальным for для инструкции и не может быть повторно объявлено в expression или statement . Обратите внимание, что auto ключевое слово предпочтительнее в for-range-declaration части инструкции.
Новые возможности Visual Studio 2017: циклы на основе for диапазона больше не требуют этого begin() и end() возвращают объекты одного типа. Это позволяет end() возвращать объект sentinel, например используемый диапазонами, как определено в предложении Ranges-V3. Дополнительные сведения см. в разделе «Обобщение цикла на основе For диапазона» и библиотеки range-v3 на GitHub.
В этом коде показано, как использовать циклы на основе for диапазона для итерации по массиву и вектору:
// range-based-for.cpp // compile by using: cl /EHsc /nologo /W4 #include #include using namespace std; int main() < // Basic 10-element integer array. int x[10] = < 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 >; // Range-based for loop to iterate through the array. for( int y : x ) < // Access by value using a copy declared as a specific type. // Not preferred. cout cout << endl; // The auto keyword causes type inference to be used. Preferred. for( auto y : x ) < // Copy of 'x', almost always undesirable cout cout << endl; for( auto &y : x ) < // Type inference by reference. // Observes and/or modifies in-place. Preferred when modify is needed. cout cout << endl; for( const auto &y : x ) < // Type inference by const reference. // Observes in-place. Preferred when no modify is needed. cout cout v; for (int i = 0; i < 10; ++i) < v.push_back(i + 0.14159); >// Range-based for loop to iterate through the vector, observing in-place. for( const auto &j : v ) < cout cout
Результат выглядит так:
1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 end of integer array test 0.14159 1.14159 2.14159 3.14159 4.14159 5.14159 6.14159 7.14159 8.14159 9.14159 end of vector test
Цикл на основе for диапазона завершается при выполнении одного из них statement : a break return , или goto к помеченной инструкции за пределами цикла на основе for диапазона. Оператор continue в цикле на основе for диапазона завершает только текущую итерацию.
Имейте в виду эти факты о зависимости от for диапазона:
- Такие циклы автоматически распознают массивы.
- Такие циклы автоматически распознают контейнеры с методами .begin() и .end() .
- Для всех остальных итераторов в них используются поиск, зависящий от аргументов ( begin() и end() ).
auto (C++)
Выводит тип объявленной переменной из выражения инициализации.
Стандарт C++ определяет исходный и измененный смысл для этого ключевое слово. Перед Visual Studio 2010 auto ключевое слово объявляет переменную в классе автоматического хранения, то есть переменную, которая имеет локальное время существования. Начиная с Visual Studio 2010, ключевое слово объявляет переменную, auto тип которой выводится из выражения инициализации в объявлении. Параметр /Zc:auto[-] компилятора управляет значением auto ключевое слово.
Синтаксис
auto declaratorinitializer ;
[](auto param1 , auto param2 ) <>;
Замечания
auto Ключевое слово направляет компилятору использовать выражение инициализации объявленной переменной или лямбда-параметра выражения, чтобы вывести его тип.
Рекомендуется использовать auto ключевое слово для большинства ситуаций, если вы не хотите преобразовать, так как это дает следующие преимущества:
- Надежность. Если тип выражения изменяется( включая изменение типа возвращаемого значения функции), он просто работает.
- Производительность. Вы гарантируете, что преобразования отсутствуют.
- Удобство использования: вам не нужно беспокоиться о проблемах орфографии имен типов и опечаток.
- Эффективность: код может быть более эффективным.
Варианты преобразования, в которых может не потребоваться использовать auto :
- Вы хотите конкретный тип и ничего другого не будет делать.
- Например, (valarray+valarray) вспомогательные типы шаблонов выражений.
Чтобы использовать auto ключевое слово, используйте его вместо типа для объявления переменной и укажите выражение инициализации. Кроме того, можно изменить auto ключевое слово с помощью описателей и деклараторов, таких как const , volatile указатель ( * ), ссылка ( & ) и ссылка rvalue ( && ). Компилятор вычисляет выражение инициализации, а затем использует эти сведения, чтобы вывести тип переменной.
auto Выражение инициализации может принимать несколько форм:
- Синтаксис универсальной инициализации, например auto a < 42 >; .
- Синтаксис назначения, например auto b = 0; .
- Синтаксис универсального назначения, который объединяет две предыдущие формы, например auto c = < 3.14159 >; .
- Прямая инициализация или синтаксис стиля конструктора, например auto d( 1.41421f ); .
Дополнительные сведения см. в разделе "Инициализаторы" и примеры кода далее в этом документе.
Если auto используется для объявления параметра цикла в инструкции на основе for диапазона, он использует другой синтаксис инициализации, например for (auto& i : iterable) do_action(i); . Дополнительные сведения см. в инструкции на основе for диапазона (C++).
auto Ключевое слово — это заполнитель для типа, но он не является типом. auto Поэтому ключевое слово нельзя использовать в приведениях или операторах, таких как sizeof и (для C++/CLI). typeid
Удобство
auto Ключевое слово — это простой способ объявить переменную с сложным типом. Например, можно объявить auto переменную, в которой выражение инициализации включает шаблоны, указатели на функции или указатели на элементы.
Можно также использовать auto для объявления и инициализации переменной в лямбда-выражение. Вы не сможете самостоятельно объявить тип переменной, поскольку тип лямбда-выражения известен только компилятору. Дополнительные сведения см. в примерах лямбда-выражений.
Отслеживание возвращаемых типов
Для записи библиотек шаблонов можно использовать auto вместе с decltype описателями типов. Используйте и decltype объявите auto шаблон функции, возвращаемый тип которого зависит от типов его аргументов шаблона. Кроме того, используйте auto и decltype объявите шаблон функции, который упаковывает вызов другой функции, а затем возвращает любой тип возвращаемого значения этой другой функции. Дополнительные сведения см. в разделе decltype .
Ссылки и cv-квалификаторы
Использование auto ссылок, const квалификаторов и volatile квалификаторов. Рассмотрим следующий пример:
// cl.exe /analyze /EHsc /W4 #include using namespace std; int main( )
В предыдущем примере myAuto является не int ссылкой int , поэтому выходные данные 11 11 не 11 12 так, как было бы в случае, если квалификатор ссылок не был удален auto .
Вычет типов с фигурными инициализаторами (C++14)
В следующем примере кода показано, как инициализировать переменную auto с помощью фигурных скобок. Обратите внимание на разницу между B и C и между A и E.
#include int main() < // std::initializer_listauto A = < 1, 2 >; // std::initializer_list auto B = < 3 >; // int auto C< 4 >; // C3535: cannot deduce type for 'auto' from initializer list' auto D = < 5, 6.7 >; // C3518 in a direct-list-initialization context the type for 'auto' // can only be deduced from a single initializer expression auto E< 8, 9 >; return 0; >
Ограничения и сообщения об ошибках
В следующей таблице перечислены ограничения на использование auto ключевое слово и соответствующее диагностическое сообщение об ошибке, которое компилятор выдает.
| Номер ошибки | Description |
|---|---|
| C3530 | Ключевое слово auto нельзя сочетать с любым другим описательом типа. |
| C3531 | Символ, объявленный с auto ключевое слово, должен иметь инициализатор. |
| C3532 | Вы неправильно использовали auto ключевое слово для объявления типа. Например, был объявлен тип возвращаемого значения метода или массив. |
| C3533, C3539 | Аргумент параметра или шаблона нельзя объявить с auto помощью ключевое слово. |
| C3535 | Невозможно объявить метод или параметр шаблона с auto помощью ключевое слово. |
| C3536 | Символ нельзя использовать перед инициализацией. На практике это означает, что переменная не может использоваться для инициализации себя. |
| C3537 | Нельзя привести к типу, объявленному с auto помощью ключевое слово. |
| C3538 | Все символы в списке декларатора, объявленные с auto помощью ключевое слово, должны разрешаться в один и тот же тип. Дополнительные сведения см. в объявлениях и определениях. |
| C3540, C3541 | Операторы sizeof и typeid нельзя применять к символу, объявленному с auto помощью ключевое слово. |
Примеры
Эти фрагменты кода иллюстрируют некоторые способы auto использования ключевое слово.
Следующие объявления эквивалентны. В первой инструкции переменная j объявляется типом int . Во втором операторе переменная k выводится в тип int , так как выражение инициализации (0) является целым числом.
int j = 0; // Variable j is explicitly type int. auto k = 0; // Variable k is implicitly type int because 0 is an integer.
Следующие объявления эквивалентны, но второе объявление проще первого. Одной из самых убедительных причин использования auto ключевое слово является простота.
map>::iterator i = m.begin(); auto i = m.begin();
Следующий фрагмент кода объявляет тип переменных iter и elem при for запуске циклов диапазона for .
// cl /EHsc /nologo /W4 #include using namespace std; int main() < dequedqDoubleData(10, 0.1); for (auto iter = dqDoubleData.begin(); iter != dqDoubleData.end(); ++iter) < /* . */ >// prefer range-for loops with the following information in mind // (this applies to any range-for with auto, not just deque) for (auto elem : dqDoubleData) // COPIES elements, not much better than the previous examples < /* . */ >for (auto& elem : dqDoubleData) // observes and/or modifies elements IN-PLACE < /* . */ >for (const auto& elem : dqDoubleData) // observes elements IN-PLACE < /* . */ >>
Следующий фрагмент кода использует new оператор и объявление указателя для объявления указателей.
double x = 12.34; auto *y = new auto(x), **z = new auto(&x);
В следующем примере кода объявлено несколько символов в каждом операторе объявления. Обратите внимание, что все символы во всех операторах разрешаются к одному и тому же типу.
auto x = 1, *y = &x, **z = &y; // Resolves to int. auto a(2.01), *b (&a); // Resolves to double. auto c = 'a', *d(&c); // Resolves to char. auto m = 1, &n = m; // Resolves to int.
В этом примере кода используется условный оператор ( ?: ). Переменная x здесь объявляется как целочисленная переменная со значением 200.
int v1 = 100, v2 = 200; auto x = v1 > v2 ? v1 : v2;
Следующий фрагмент кода инициализирует переменную типа, переменную x int к типу const int и переменную fp y указателю на функцию, возвращающую тип int .
int f(int x) < return x; >int main() < auto x = f(0); const auto& y = f(1); int (*p)(int x); p = f; auto fp = p; //. >
Ключевое слово `auto`
Ключевое слово auto означает, что переменная находится в automatic storage и время жизни такой переменной local lifetime. Другими словами, мы указывали, что данная переменная лежит в стеке, но так как все переменные созданные в функциях как
int a = 10;
уже и так подразумивается, что они стековые - то это ключевое слово безсмысленное.
Начиная с С++11
Начиная с С++11 ключевое слово auto обретает новую жизнь. Оно говорит, что компилятор на этапе компиляции должен определить тип переменной на основе типа инициализируемого выражения.
#include #include #include using namespace std; class Foo < public: Foo( int x ) < >>; int main() < std::vector> arr; auto a = 166LL; auto b = 'a' + true; auto c = Foo(3); auto d = arr.begin(); cout
Особенности auto
- Переменная auto должна быть объязательно проинициализирована
- Переменная auto не может быть класс-мембером
- Переменная auto не может быть параметром функции до С++14
http://ideone.com/n7dZge - Тип auto не может быть возвращаемым типом функции до С++14. http://rextester.com/AFDFD63587
Холивар
Cторонники: есть типы данных в С++, которые портят читаемость когда своей длинной (про итераторы std::vector, к примеру) и хотелось бы писать меньше. Для modern C++ в условиях метапрограммирования возможность возврата типа auto делает шаблон гибким.
Противники: тип auto бьет по читабельности кода. Приходится гадать, что за переменная и делать лишнее действие в IDE наводя мышкой, чтобы понять что за тип. Такого рода "динамический тип" встает в разрез определению, что С++ строготипизированный язык.
Я же за использование auto в меру. Не надо впадаться в крайности.
Универсальный конструктор Auto
С приходом C++11 появилась возможность объявлять переменные с типом auto, а компилятор сам определил фактический тип переменной, на основе типа инициализируемого значения. Это удобно, когда мы хотим проинициализировать переменную тип которой слишком сложный, либо неизвестен, либо он нам не очень важен, либо просто для простоты.
auto f = []()<>; //указатель на функцию auto r = foo(10); //тип возвращаемый функцией foo for (auto i = 0; i
… и т.д. То есть в левой части равенства у нас автоматический тип auto, а в правой части значение четко определенного типа. А теперь представим, что у нас все наоборот:
int a = auto(10);
Слева у нас четко описанный тип, а справа что-то неизвестное. Конечно в данном примере нет смысла вызывать универсальный конструктор, когда можно было просто присвоить к переменной a значение 10:
int a = 10;
Или в крайнем случае вызвать его конструктор:
int a(10);
А если это аргумент функции, например:
str::map myMap; myMap.insert(pair('a', 10));
Метод insert шаблонного класса map ожидает четко указанный тип, но нам приходится писать «pair» снова и снова при каждом вызове. Хорошо если наш тип простой, а если там шаблон на шаблоне и шаблоном погоняет? Тут нам поможет автоматический конструктор:
myMap.insert(auto('a', 10));
Функция, Конструктор или Оператор auto, не важно что это, создаст нам какой-то объект, который подходит под описание входного параметра метода insert.
Но к сожалению в языке C++ пока нет такой методики создания объектов, но я надеюсь, что когда-нибудь она появится, а пока хочу представить свой вариант реализации такой задачи. Конечно же основная цель упростить написание кода, но не менее важная задача не навредить нашей программе: она не должна увеличится в объеме, замедлиться в выполнении и т.п. В идеале она должна быть идентична той, что если бы мы писали без конструктора auto.
И так. Нам нужно создать какой-то универсальный объект, который бы мог преобразоваться в запрашиваемый тип и сделать это на этапе компиляции. Конечно я не беру во внимание оптимизацию компиляции O0, Og и т.п. возьмем оптимизацию Os.
Наш объект (контейнер) должен принять все входные аргументы, сохранить их у себя, а при преобразовании к запрашиваемому типу попытаться вызвать его конструктор с передачей всего, что когда-то было передано ему.
Для начала нам понадобится универсальная переменная, способная хранить как копии объектов, так и указатели и ссылки. Хотя указатель и копия в данном случае одно и тоже, а вот со ссылкой сложнее:
template struct Value < constexpr Value(T v): v(v) <>constexpr T get() T v; >; template struct Value < constexpr Value(T& v): v(&v) <>constexpr T& get() T* v; >;
Здесь все относительно просто, принимаем любой аргумент и сохраняем его копию, а в случае со ссылкой преобразуем ее в указатель и обратно.
Сама специализация необходима, чтобы отсеять аргументы по ссылке, чтобы создать универсальность, а вот без преобразования в указатель компилятор отказывается выполнять на этапе компиляции, хотя казалось бы в чем разница, храним мы в указателе или в ссылке.
Теперь нам нужно создать универсальный контейнер с неограниченным числом аргументов произвольных типов:
template struct Container < constexpr Container() <>template constexpr operator T() ();> template T constexpr get(Values&&. v) >; template struct Container < constexpr Container(const Type&& arg, const Types&&. args): arg(arg), args((Types&&)args. ) <>template constexpr operator T() ();> template T constexpr get(Values&&. v) ((Values&&)v. arg.get());> Value arg; Container args; >;
Рекурсивный контейнер принимает неограниченное число аргументов различных типов и помещает первый аргумент к себе, а остальные во вложенный контейнер с оставшимися аргументами, пока не дойдем до последнего аргумента.
Также этот контейнер имеет оператор преобразования к любому требуемому типу вызывая рекурсивный метод get с вложением всех имеющихся аргументов.
Все аргументы передаются в качестве rvalue аргументов до самого конечного получателя Value, чтобы не потерять ссылки.
Ну и наконец сам универсальный конструктор Auto. Я его назвал с большой буквы, т.к. ключевое слово auto, сами понимаете, уже занято. А учитывая, что эта функция выполняет роль конструктора заглавная буква ей к лицу.
template constexpr Container Auto(Types&&. args)
Напоследок переместим класс Value в private область класса Container и получится следующее:
template struct Container < constexpr Container() <>template constexpr operator T() ();> template T constexpr get(Values&&. v) >; template struct Container < constexpr Container(const Type&& arg, const Types&&. args): arg(arg), args((Types&&)args. ) <>template constexpr operator T() ();> template T constexpr get(Values&&. v) ((Values&&)v. arg.get());> private: template struct Value < constexpr Value(T v): v(v) <>constexpr T get() T v; >; template struct Value < constexpr Value(T& v): v(&v) <>constexpr T& get() T* v; >; Value arg; Container args; >; template constexpr Container Auto(Types&&. args)
Все преобразование выполняется на этапе компиляции и ни чем не отличается от прямого вызова конструкторов.
Правда есть небольшие неудобства — ни один суфлер не сможет вам предложить варианты входных аргументов.