clang-tidy readability-container-size-empty
labelled unknown
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
| id | result | title | hazard |
|---|---|---|---|
| r1-p001 | fix ok | Exception Safety During Container Clear | exceptions thrown inside the rewritten code, RAII order of destruction |
| r1-p002 | fix ok prediction off | Iterator Invalidation in Range-Based Loop Rewrite | iterators invalidated or consumed when a loop is rewritten to range-based for |
| r1-p003 | silent prediction off | Side Effects in Size() via Overloaded Operator | implicit conversions, overloaded operators and user-defined conversions with side effects |
| r1-p004 | fix ok | Unsigned Underflow in Size Comparison | integer promotion, signedness, overflow, narrowing in the rewritten expression |
| r1-p005 | fix ok | size() Accuracy vs Logical Emptiness | 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. |
| r1-p006 | fix ok | Comment Attachment Lost in Rewrite | Comments 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