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 (vector<int>::iterator it = begin(v); it != end(v); ++it) 96 cout << *it; 97 98 // reasonable conversion 99 for (vector<int>::iterator it = std::begin(v); it != std::end(v); ++it) 100 cout << *it; 101 102 // reasonable conversion 103 for (int i = 0; i < v.size(); ++i) 104 cout << v[i]; 105 106 // reasonable conversion 107 for (int i = 0; i < size(v); ++i) 108 cout << v[i]; 109 110After applying the check with minimum confidence level set to `reasonable` (default): 111 112.. code-block:: c++ 113 114 const int N = 5; 115 int arr[] = {1,2,3,4,5}; 116 vector<int> v; 117 v.push_back(1); 118 v.push_back(2); 119 v.push_back(3); 120 121 // safe conversion 122 for (auto & elem : arr) 123 cout << elem; 124 125 // reasonable conversion 126 for (auto & elem : v) 127 cout << elem; 128 129 // reasonable conversion 130 for (auto & elem : v) 131 cout << elem; 132 133Reverse Iterator Support 134------------------------ 135 136The converter is also capable of transforming iterator loops which use 137``rbegin`` and ``rend`` for looping backwards over a container. Out of the box 138this will automatically happen in C++20 mode using the ``ranges`` library, 139however the check can be configured to work without C++20 by specifying a 140function to reverse a range and optionally the header file where that function 141lives. 142 143.. option:: UseCxx20ReverseRanges 144 145 When set to true convert loops when in C++20 or later mode using 146 ``std::ranges::reverse_view``. 147 Default value is ``true``. 148 149.. option:: MakeReverseRangeFunction 150 151 Specify the function used to reverse an iterator pair, the function should 152 accept a class with ``rbegin`` and ``rend`` methods and return a 153 class with ``begin`` and ``end`` methods that call the ``rbegin`` and 154 ``rend`` methods respectively. Common examples are ``ranges::reverse_view`` 155 and ``llvm::reverse``. 156 Default value is an empty string. 157 158.. option:: MakeReverseRangeHeader 159 160 Specifies the header file where :option:`MakeReverseRangeFunction` is 161 declared. For the previous examples this option would be set to 162 ``range/v3/view/reverse.hpp`` and ``llvm/ADT/STLExtras.h`` respectively. 163 If this is an empty string and :option:`MakeReverseRangeFunction` is set, 164 the check will proceed on the assumption that the function is already 165 available in the translation unit. 166 This can be wrapped in angle brackets to signify to add the include as a 167 system include. 168 Default value is an empty string. 169 170.. option:: IncludeStyle 171 172 A string specifying which include-style is used, `llvm` or `google`. Default 173 is `llvm`. 174 175Limitations 176----------- 177 178There are certain situations where the tool may erroneously perform 179transformations that remove information and change semantics. Users of the tool 180should be aware of the behavior and limitations of the check outlined by 181the cases below. 182 183Comments inside loop headers 184^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 185 186Comments inside the original loop header are ignored and deleted when 187transformed. 188 189.. code-block:: c++ 190 191 for (int i = 0; i < N; /* This will be deleted */ ++i) { } 192 193Range-based loops evaluate end() only once 194^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 195 196The C++11 range-based for loop calls ``.end()`` only once during the 197initialization of the loop. If in the original loop ``.end()`` is called after 198each iteration the semantics of the transformed loop may differ. 199 200.. code-block:: c++ 201 202 // The following is semantically equivalent to the C++11 range-based for loop, 203 // therefore the semantics of the header will not change. 204 for (iterator it = container.begin(), e = container.end(); it != e; ++it) { } 205 206 // Instead of calling .end() after each iteration, this loop will be 207 // transformed to call .end() only once during the initialization of the loop, 208 // which may affect semantics. 209 for (iterator it = container.begin(); it != container.end(); ++it) { } 210 211.. _IncorrectRiskyTransformation: 212 213As explained above, calling member functions of the container in the body 214of the loop is considered `risky`. If the called member function modifies the 215container the semantics of the converted loop will differ due to ``.end()`` 216being called only once. 217 218.. code-block:: c++ 219 220 bool flag = false; 221 for (vector<T>::iterator it = vec.begin(); it != vec.end(); ++it) { 222 // Add a copy of the first element to the end of the vector. 223 if (!flag) { 224 // This line makes this transformation 'risky'. 225 vec.push_back(*it); 226 flag = true; 227 } 228 cout << *it; 229 } 230 231The original code above prints out the contents of the container including the 232newly added element while the converted loop, shown below, will only print the 233original contents and not the newly added element. 234 235.. code-block:: c++ 236 237 bool flag = false; 238 for (auto & elem : vec) { 239 // Add a copy of the first element to the end of the vector. 240 if (!flag) { 241 // This line makes this transformation 'risky' 242 vec.push_back(elem); 243 flag = true; 244 } 245 cout << elem; 246 } 247 248Semantics will also be affected if ``.end()`` has side effects. For example, in 249the case where calls to ``.end()`` are logged the semantics will change in the 250transformed loop if ``.end()`` was originally called after each iteration. 251 252.. code-block:: c++ 253 254 iterator end() { 255 num_of_end_calls++; 256 return container.end(); 257 } 258 259Overloaded operator->() with side effects 260^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 261 262Similarly, if ``operator->()`` was overloaded to have side effects, such as 263logging, the semantics will change. If the iterator's ``operator->()`` was used 264in the original loop it will be replaced with ``<container element>.<member>`` 265instead due to the implicit dereference as part of the range-based for loop. 266Therefore any side effect of the overloaded ``operator->()`` will no longer be 267performed. 268 269.. code-block:: c++ 270 271 for (iterator it = c.begin(); it != c.end(); ++it) { 272 it->func(); // Using operator->() 273 } 274 // Will be transformed to: 275 for (auto & elem : c) { 276 elem.func(); // No longer using operator->() 277 } 278 279Pointers and references to containers 280^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 281 282While most of the check's risk analysis is dedicated to determining whether 283the iterator or container was modified within the loop, it is possible to 284circumvent the analysis by accessing and modifying the container through a 285pointer or reference. 286 287If the container were directly used instead of using the pointer or reference 288the following transformation would have only been applied at the `risky` 289level since calling a member function of the container is considered `risky`. 290The check cannot identify expressions associated with the container that are 291different than the one used in the loop header, therefore the transformation 292below ends up being performed at the `safe` level. 293 294.. code-block:: c++ 295 296 vector<int> vec; 297 298 vector<int> *ptr = &vec; 299 vector<int> &ref = vec; 300 301 for (vector<int>::iterator it = vec.begin(), e = vec.end(); it != e; ++it) { 302 if (!flag) { 303 // Accessing and modifying the container is considered risky, but the risk 304 // level is not raised here. 305 ptr->push_back(*it); 306 ref.push_back(*it); 307 flag = true; 308 } 309 } 310 311OpenMP 312^^^^^^ 313 314As range-based for loops are only available since OpenMP 5, this check should 315not be used on code with a compatibility requirement of OpenMP prior to 316version 5. It is **intentional** that this check does not make any attempts to 317exclude incorrect diagnostics on OpenMP for loops prior to OpenMP 5. 318 319To prevent this check to be applied (and to break) OpenMP for loops but still be 320applied to non-OpenMP for loops the usage of ``NOLINT`` (see 321:ref:`clang-tidy-nolint`) on the specific for loops is recommended. 322