fixhunt

clang-tidy readability-container-size-empty

labelled unknown
Runs / 2026-10-17T20-07-13-128Z-a3zg-clang-tidy-readability-container-size-emptyclang-tidy 21.1.82026-10-17 20:07 UTC6 programs × 1 round(s)6 programs, 5 fired, 5 fixed, 0 findings, 0 rejectedmodel cost $0.0018

Analysis Nemotron 3 Super

The rule flags comparisons of container size() to zero using == or != operators.

Fix: Replace 'container.size() == 0' with 'container.empty()' and 'container.size() != 0' with '!container.empty()'

12 hazards
  • The expression may have side effects if size() is overloaded with non-const or mutating behavior, and empty() may not replicate those effects.
  • If the container type lacks an empty() member function, the rewrite will fail to compile.
  • The return type of size() might be unsigned, and comparing to zero could trigger warnings that empty() avoids, but the logical equivalence holds only if size() accurately reflects emptiness.
  • In templates, empty() might be resolved via ADL or inheritance differently than size(), leading to different overloads being selected.
  • If size() is virtual and overridden in a derived class, empty() might not be overridden, breaking behavioral equivalence.
  • The rewrite may interfere with debugging or logging if size() has breakpoints or side effects absent in empty().
  • If the container is actually a range or view without an empty() method but with size(), the rewrite is invalid.
  • Comments attached to the original size() expression may be lost or misplaced during the rewrite.
  • The rewrite could change the expression's type if size() returns a proxy type with operator== defined but empty() returns a different type.
  • In constexpr contexts, size() and empty() might differ in evaluation eligibility due to implementation constraints.
  • If the container is a custom type where empty() has different complexity guarantees (e.g., O(n) vs O(1)), performance characteristics may change.
  • The macro expansion context might cause the rewrite to alter precedence if size() is part of a larger expression involving operators like ?, :, or ,.

Programs

idresulttitlehazard
r1-p001fix okException Safety During Container Clearexceptions thrown inside the rewritten code, RAII order of destruction
r1-p002fix ok prediction offIterator Invalidation in Range-Based Loop Rewriteiterators invalidated or consumed when a loop is rewritten to range-based for
r1-p003silent prediction offSide Effects in Size() via Overloaded Operatorimplicit conversions, overloaded operators and user-defined conversions with side effects
r1-p004fix okUnsigned Underflow in Size Comparisoninteger promotion, signedness, overflow, narrowing in the rewritten expression
r1-p005fix oksize() Accuracy vs Logical EmptinessThe return type of size() might be unsigned, and comparing to zero could trigger warnings that empty() avoids, but the logical equivalence holds only if size() accurately reflects emptiness.
r1-p006fix okComment Attachment Lost in RewriteComments attached to the original size() expression may be lost or misplaced during the rewrite.

Sources

r1-p001 · Exception Safety During Container Clear
1#include <iostream>
2#include <vector>
3struct ThrowOnCopy {
4 int v;
5 ThrowOnCopy(int x) : v(x) {}
6 ThrowOnCopy(const ThrowOnCopy&) { throw "copy"; }
7};
8int main() {
9 std::vector<ThrowOnCopy> c{ThrowOnCopy(1), ThrowOnCopy(2)};
10 try {
11 if (c.size() == 0) {
12 std::printf("empty\n");
13 } else {
14 c.clear(); // clear does not throw
15 if (c.size() == 0) {
16 std::printf("now empty\n");
17 }
18 }
19 } catch (const char* e) {
20 std::printf("caught: %s\n", e);
21 }
22 return 0;
23}
--- orig.cpp	2026-10-17 16:07:21.391175901 -0400
+++ fixed.cpp	2026-10-17 16:07:25.159069953 -0400
@@ -8,11 +8,11 @@
 int main() {
     std::vector<ThrowOnCopy> c{ThrowOnCopy(1), ThrowOnCopy(2)};
     try {
-        if (c.size() == 0) {
+        if (c.empty()) {
             std::printf("empty\n");
         } else {
             c.clear(); // clear does not throw
-            if (c.size() == 0) {
+            if (c.empty()) {
                 std::printf("now empty\n");
             }
         }
r1-p002 · Iterator Invalidation in Range-Based Loop Rewrite
1#include <iostream>
2#include <vector>
3int main() {
4 std::vector<int> v{1,2,3};
5 auto it = v.begin();
6 while (v.size() != 0) {
7 std::printf("%d ", *it); ++it;
8 v.pop_back();
9 }
10 std::printf("\n");
11 return 0;
12}
--- orig.cpp	2026-10-17 16:07:27.284918093 -0400
+++ fixed.cpp	2026-10-17 16:07:30.643317673 -0400
@@ -3,7 +3,7 @@
 int main() {
     std::vector<int> v{1,2,3};
     auto it = v.begin();
-    while (v.size() != 0) {
+    while (!v.empty()) {
         std::printf("%d ", *it); ++it;
         v.pop_back();
     }
1 2 3
r1-p003 · Side Effects in Size() via Overloaded Operator
1#include <iostream>
2struct Counter {
3 mutable int count = 0;
4 size_t size() const { return ++count; }
5};
6int main() {
7 Counter c;
8 bool b1 = (c.size() == 0);
9 bool b2 = (c.size() == 0);
10 std::printf("%d %d\n", b1, b2);
11 return 0;
12}
0 0
r1-p004 · Unsigned Underflow in Size Comparison
1#include <iostream>
2#include <vector>
3int main() {
4 std::vector<unsigned> v{1};
5 v.pop_back();
6 if (v.size() == 0) {
7 std::printf("zero\n");
8 } else {
9 std::printf("%zu\n", v.size());
10 }
11 if (v.size() != 0) {
12 std::printf("non-zero\n");
13 } else {
14 std::printf("zero again\n");
15 }
16 return 0;
17}
--- orig.cpp	2026-10-17 16:07:34.965586923 -0400
+++ fixed.cpp	2026-10-17 16:07:38.322081525 -0400
@@ -3,12 +3,12 @@
 int main() {
     std::vector<unsigned> v{1};
     v.pop_back();
-    if (v.size() == 0) {
+    if (v.empty()) {
         std::printf("zero\n");
     } else {
         std::printf("%zu\n", v.size());
     }
-    if (v.size() != 0) {
+    if (!v.empty()) {
         std::printf("non-zero\n");
     } else {
         std::printf("zero again\n");
zero
zero again
r1-p005 · size() Accuracy vs Logical Emptiness
1#include <iostream>
2#include <vector>
3struct Broken {
4 std::vector<int> data;
5 size_t size() const { return data.size() + 1; }
6 bool empty() const { return data.empty(); }
7};
8int main() {
9 Broken b;
10 b.data.push_back(1);
11 std::printf("size==0: %d\n", (b.size() == 0));
12 std::printf("empty(): %d\n", b.empty());
13 return 0;
14}
--- orig.cpp	2026-10-17 16:07:40.377356827 -0400
+++ fixed.cpp	2026-10-17 16:07:43.725678931 -0400
@@ -8,7 +8,7 @@
 int main() {
     Broken b;
     b.data.push_back(1);
-    std::printf("size==0: %d\n", (b.size() == 0));
+    std::printf("size==0: %d\n", (b.empty()));
     std::printf("empty(): %d\n", b.empty());
     return 0;
 }
size==0: 0
empty(): 0
r1-p006 · Comment Attachment Lost in Rewrite
1#include <iostream>
2#include <vector>
3int main() {
4 std::vector<int> v{1};
5 if (v.size() == 0) { // check if empty before processing
6 std::printf("empty\n");
7 } else {
8 std::printf("not empty\n");
9 }
10 return 0;
11}
--- orig.cpp	2026-10-17 16:07:45.743131279 -0400
+++ fixed.cpp	2026-10-17 16:07:49.053346311 -0400
@@ -2,7 +2,7 @@
 #include <vector>
 int main() {
     std::vector<int> v{1};
-    if (v.size() == 0) { // check if empty before processing
+    if (v.empty()) { // check if empty before processing
         std::printf("empty\n");
     } else {
         std::printf("not empty\n");
not empty