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