Skip to content

fix(cpp): a nullptr argument picks the std::nullptr_t overload, and no call links to a deleted overload - #2554

Merged
colbymchenry merged 1 commit into
mainfrom
claude/cpp-nullptr-deleted-overloads
Oct 14, 2026
Merged

colbymchenry merged 1 commit into
mainfrom
claude/cpp-nullptr-deleted-overloads

Conversation

@colbymchenry

Copy link
Copy Markdown
Owner

Summary

Two ways a C++ call reached an overload that no compiling call reaches:

  • nullptr against std::nullptr_t. cppArgumentTypeFit scored a nullptr literal 3 against any pointer parameter. It scored only 1 against a std::nullptr_t parameter, whose type counted as a class. protocolbuffers/protobuf's Arena::UniquePtr<T> declares reset(), reset(std::nullptr_t) and reset(T* PROTOBUF_NULLABLE) = delete. So ptr.reset(nullptr) took the pointer overload. C++ takes reset(std::nullptr_t), an exact match, over a pointer conversion.
  • Deleted overloads. That reset(T*) is deleted, and so is RepeatedPtrField<Element>::Add(const Element&), beside Add(Element&&). Eighteen field.Add("foo")-style calls in protobuf's tests reached the deleted one. When overload resolution in C++ selects a deleted function, the program is ill-formed, so no compiling call reaches one.

This came out of the interaction A/B for the #2536–#2542 batch, where reset(nullptr) was the one wrong composed result.

What changed

All in name-matcher.ts:

  • cppTypeCategory: std::nullptr_t has a category of its own, null.
  • CPP_LITERAL_FIT:
    • nullptr scores 4 against std::nullptr_t, its own type, above a pointer's 3;
    • a literal 0 scores 1 against it, below an integer's 2;
    • a character, number or bool can't become one (-3).
  • cppOverloadFor: overloads declared deleted are dropped, unless every overload is. isCppDeletedFunction reads a declaration from its name to its ; or body for = delete, memoized per node and cleared with the other C++ memos.

Validation

Site-keyed edge diffs of main 24286218 against this branch. Both arms were built from one tree, kernel included.

repo removed added retargeted metadata-only
google/leveldb 0 0 0 0
fmtlib/fmt 0 0 0 0
protocolbuffers/protobuf 0 0 19 0
facebook/rocksdb 0 0 0 0
redis/redis (C, control) 0 0 0 0

Node and file counts are identical. All 19 are right:

  • ptr.reset(nullptr) (arena_unittest.cc) reaches reset(std::nullptr_t) instead of the deleted reset(T*).
  • 18 RepeatedPtrField::Add calls in repeated_ptr_field_unittest.cc reach Add(Element&&), by its out-of-line definition, instead of the deleted Add(const Element&). They include field.Add("foo"), field.Add(std::move(test_all_types)), rep.Add(v) on a const char*, and elements.Add(absl::StrCat(…)).

Tests

__tests__/cpp-nullptr-deleted-overloads.test.ts, 3 tests:

  • protobuf's shape: ptr.reset(nullptr) reaches reset(std::nullptr_t) and ptr.reset() reaches reset(). With both fixes off, as on main, it fails.
  • Set(nullptr) against Set(Widget*), Set(std::nullptr_t) and Set(int), with nothing deleted. It reaches Set(std::nullptr_t), and Set(0) reaches Set(int).
  • Scale(2) against a deleted Scale(double), Scale(int), and a deleted Scale(long) declared over two lines. It reaches Scale(int).

Mutation check (each guard broken alone; every one fails a test, at its own assertion):

guard fails
std::nullptr_t is its own category Set(nullptr)
nullptr fits std::nullptr_t over a pointer (4 vs 3) Set(nullptr)
a literal 0 fits an int over std::nullptr_t Set(0)
a deleted overload is taken only when every one is Scale(2)
a declaration is read only to its own ; or body Scale(2) (Scale(int) would read the deleted Scale(long) after it)

In protobuf's case either fix alone is enough, so each has a fixture only it decides.

Full suite: 7,047 passed, 82 skipped. The 9 failures, in 3 files and all from load on this machine, pass when those files run alone (24/24).

🤖 Generated with Claude Code

…o call links to a deleted overload

A `nullptr` argument fitted any pointer parameter better than a
`std::nullptr_t` one, which counted as a class, so protobuf's
`ptr.reset(nullptr)` on an `Arena::UniquePtr` took `reset(T*)` over
`reset(std::nullptr_t)`. That `reset(T*)` is declared `= delete`, as is
`RepeatedPtrField::Add(const Element&)`, which `field.Add("foo")` reached.
`nullptr` now fits `std::nullptr_t` exactly and a pointer by conversion, and
an overload declared deleted is taken only when every overload is.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant