1.. title:: clang-tidy - modernize-loop-convert 2 3modernize-loop-convert 4====================== 5 6This check converts ``for(...; ...; ...)`` loops to use the new range-based 7loops in C++11. 8 9Three kinds of loops can be converted: 10 11- Loops over statically allocated arrays. 12- Loops over containers, using iterators. 13- Loops over array-like containers, using ``operator[]`` and ``at()``. 14 15MinConfidence option 16-------------------- 17 18risky 19^^^^^ 20 21In loops where the container expression is more complex than just a 22reference to a declared expression (a variable, function, enum, etc.), 23and some part of it appears elsewhere in the loop, we lower our confidence 24in the transformation due to the increased risk of changing semantics. 25Transformations for these loops are marked as `risky`, and thus will only 26be converted if the minimum required confidence level is set to `risky`. 27 28.. code-block:: c++ 29 30 int arr[10][20]; 31 int l = 5; 32 33 for (int j = 0; j < 20; ++j) 34 int k = arr[l][j] + l; // using l outside arr[l] is considered risky 35 36 for (int i = 0; i < obj.getVector().size(); ++i) 37 obj.foo(10); // using 'obj' is considered risky 38 39See 40:ref:`Range-based loops evaluate end() only once<IncorrectRiskyTransformation>` 41for an example of an incorrect transformation when the minimum required confidence 42level is set to `risky`. 43 44reasonable (Default) 45^^^^^^^^^^^^^^^^^^^^ 46 47If a loop calls ``.end()`` or ``.size()`` after each iteration, the 48transformation for that loop is marked as `reasonable`, and thus will 49be converted if the required confidence level is set to `reasonable` 50(default) or lower. 51 52.. code-block:: c++ 53 54 // using size() is considered reasonable 55 for (int i = 0; i < container.size(); ++i) 56 cout << container[i]; 57 58safe 59^^^^ 60 61Any other loops that do not match the above criteria to be marked as 62`risky` or `reasonable` are marked `safe`, and thus will be converted 63if the required confidence level is set to `safe` or lower. 64 65.. code-block:: c++ 66 67 int arr[] = {1,2,3}; 68 69 for (int i = 0; i < 3; ++i) 70 cout << arr[i]; 71 72Example 73------- 74 75Original: 76 77.. code-block:: c++ 78 79 const int N = 5; 80 int arr[] = {1,2,3,4,5}; 81 vector<int> v; 82 v.push_back(1); 83 v.push_back(2); 84 v.push_back(3); 85 86 // safe conversion 87 for (int i = 0; i < N; ++i) 88 cout << arr[i]; 89 90 // reasonable conversion 91 for (vector<int>::iterator it = v.begin(); it != v.end(); ++it) 92 cout << *it; 93 94 // reasonable conversion 95 for (int i = 0; i < v.size(); ++i) 96 cout << v[i]; 97 98After applying the check with minimum confidence level set to `reasonable` (default): 99 100.. code-block:: c++ 101 102 const int N = 5; 103 int arr[] = {1,2,3,4,5}; 104 vector<int> v; 105 v.push_back(1); 106 v.push_back(2); 107 v.push_back(3); 108 109 // safe conversion 110 for (auto & elem : arr) 111 cout << elem; 112 113 // reasonable conversion 114 for (auto & elem : v) 115 cout << elem; 116 117 // reasonable conversion 118 for (auto & elem : v) 119 cout << elem; 120 121Reverse Iterator Support 122------------------------ 123 124The converter is also capable of transforming iterator loops which use 125``rbegin`` and ``rend`` for looping backwards over a container. Out of the box 126this will automatically happen in C++20 mode using the ``ranges`` library, 127however the check can be configured to work without C++20 by specifying a 128function to reverse a range and optionally the header file where that function 129lives. 130 131.. option:: UseCxx20ReverseRanges 132 133 When set to true convert loops when in C++20 or later mode using 134 ``std::ranges::reverse_view``. 135 Default value is ``true``. 136 137.. option:: MakeReverseRangeFunction 138 139 Specify the function used to reverse an iterator pair, the function should 140 accept a class with ``rbegin`` and ``rend`` methods and return a 141 class with ``begin`` and ``end`` methods methods that call the ``rbegin`` and 142 ``rend`` methods respectively. Common examples are ``ranges::reverse_view`` 143 and ``llvm::reverse``. 144 Default value is an empty string. 145 146.. option:: MakeReverseRangeHeader 147 148 Specifies the header file where :option:`MakeReverseRangeFunction` is 149 declared. For the previous examples this option would be set to 150 ``range/v3/view/reverse.hpp`` and ``llvm/ADT/STLExtras.h`` respectively. 151 If this is an empty string and :option:`MakeReverseRangeFunction` is set, 152 the check will proceed on the assumption that the function is already 153 available in the translation unit. 154 This can be wrapped in angle brackets to signify to add the include as a 155 system include. 156 Default value is an empty string. 157 158.. option:: IncludeStyle 159 160 A string specifying which include-style is used, `llvm` or `google`. Default 161 is `llvm`. 162 163Limitations 164----------- 165 166There are certain situations where the tool may erroneously perform 167transformations that remove information and change semantics. Users of the tool 168should be aware of the behaviour and limitations of the check outlined by 169the cases below. 170 171Comments inside loop headers 172^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 173 174Comments inside the original loop header are ignored and deleted when 175transformed. 176 177.. code-block:: c++ 178 179 for (int i = 0; i < N; /* This will be deleted */ ++i) { } 180 181Range-based loops evaluate end() only once 182^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 183 184The C++11 range-based for loop calls ``.end()`` only once during the 185initialization of the loop. If in the original loop ``.end()`` is called after 186each iteration the semantics of the transformed loop may differ. 187 188.. code-block:: c++ 189 190 // The following is semantically equivalent to the C++11 range-based for loop, 191 // therefore the semantics of the header will not change. 192 for (iterator it = container.begin(), e = container.end(); it != e; ++it) { } 193 194 // Instead of calling .end() after each iteration, this loop will be 195 // transformed to call .end() only once during the initialization of the loop, 196 // which may affect semantics. 197 for (iterator it = container.begin(); it != container.end(); ++it) { } 198 199.. _IncorrectRiskyTransformation: 200 201As explained above, calling member functions of the container in the body 202of the loop is considered `risky`. If the called member function modifies the 203container the semantics of the converted loop will differ due to ``.end()`` 204being called only once. 205 206.. code-block:: c++ 207 208 bool flag = false; 209 for (vector<T>::iterator it = vec.begin(); it != vec.end(); ++it) { 210 // Add a copy of the first element to the end of the vector. 211 if (!flag) { 212 // This line makes this transformation 'risky'. 213 vec.push_back(*it); 214 flag = true; 215 } 216 cout << *it; 217 } 218 219The original code above prints out the contents of the container including the 220newly added element while the converted loop, shown below, will only print the 221original contents and not the newly added element. 222 223.. code-block:: c++ 224 225 bool flag = false; 226 for (auto & elem : vec) { 227 // Add a copy of the first element to the end of the vector. 228 if (!flag) { 229 // This line makes this transformation 'risky' 230 vec.push_back(elem); 231 flag = true; 232 } 233 cout << elem; 234 } 235 236Semantics will also be affected if ``.end()`` has side effects. For example, in 237the case where calls to ``.end()`` are logged the semantics will change in the 238transformed loop if ``.end()`` was originally called after each iteration. 239 240.. code-block:: c++ 241 242 iterator end() { 243 num_of_end_calls++; 244 return container.end(); 245 } 246 247Overloaded operator->() with side effects 248^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 249 250Similarly, if ``operator->()`` was overloaded to have side effects, such as 251logging, the semantics will change. If the iterator's ``operator->()`` was used 252in the original loop it will be replaced with ``<container element>.<member>`` 253instead due to the implicit dereference as part of the range-based for loop. 254Therefore any side effect of the overloaded ``operator->()`` will no longer be 255performed. 256 257.. code-block:: c++ 258 259 for (iterator it = c.begin(); it != c.end(); ++it) { 260 it->func(); // Using operator->() 261 } 262 // Will be transformed to: 263 for (auto & elem : c) { 264 elem.func(); // No longer using operator->() 265 } 266 267Pointers and references to containers 268^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 269 270While most of the check's risk analysis is dedicated to determining whether 271the iterator or container was modified within the loop, it is possible to 272circumvent the analysis by accessing and modifying the container through a 273pointer or reference. 274 275If the container were directly used instead of using the pointer or reference 276the following transformation would have only been applied at the `risky` 277level since calling a member function of the container is considered `risky`. 278The check cannot identify expressions associated with the container that are 279different than the one used in the loop header, therefore the transformation 280below ends up being performed at the `safe` level. 281 282.. code-block:: c++ 283 284 vector<int> vec; 285 286 vector<int> *ptr = &vec; 287 vector<int> &ref = vec; 288 289 for (vector<int>::iterator it = vec.begin(), e = vec.end(); it != e; ++it) { 290 if (!flag) { 291 // Accessing and modifying the container is considered risky, but the risk 292 // level is not raised here. 293 ptr->push_back(*it); 294 ref.push_back(*it); 295 flag = true; 296 } 297 } 298 299OpenMP 300^^^^^^ 301 302As range-based for loops are only available since OpenMP 5, this check should 303not been used on code with a compatibility requirements of OpenMP prior to 304version 5. It is **intentional** that this check does not make any attempts to 305exclude incorrect diagnostics on OpenMP for loops prior to OpenMP 5. 306 307To prevent this check to be applied (and to break) OpenMP for loops but still be 308applied to non-OpenMP for loops the usage of ``NOLINT`` (see 309:ref:`clang-tidy-nolint`) on the specific for loops is recommended. 310