llvm / llvm/llvm-project

[Clang] Clang sometimes incorrectly makes not-exactly-matching deallocation functions cause ambiguity

Open
#177,890 6 comments 0 reactions 0 assignees View on GitHub
clang:frontend diverges-from:gcc diverges-from:msvc
Dominant language
LLVM
Stars
40.5k
Forks
18.7k
PR merge metrics
PR metrics pending

Description

In the following example, both MSVC and GCC doesn't think the deleted deallocation function overloads cause ambiguity and correctly call corresponding deallocation functions when an exception is thrown on initialization. But Clang thinks the deleted overloads cause ambiguity.

Per [[expr.new]/29](https://eel.is/c++draft/expr.new#29), perhaps MSVC and GCC are correct and Clang is wrong.

Link: https://godbolt.org/z/xf4qrqcME

```C++
#include
#include
#include

template
void operator delete(void*, Args&&...) = delete;

template
void operator delete[](void*, Args&&...) = delete;

struct ThrowingDefaultCtor {
struct UniqueException {};

ThrowingDefaultCtor() { throw UniqueException{}; }
};

void f() {
if (auto p = new (std::nothrow) ThrowingDefaultCtor)
delete p;
}

void g() {
if (auto p = new (std::nothrow) ThrowingDefaultCtor[2])
delete[] p;
}

void* operator new(std::size_t n, const std::nothrow_t&) noexcept { // replacing version
if (n == 0)
++n;
auto p = std::malloc(n);
std::puts("operator new(size_t, nothrow_t)");
return p;
}

void* operator new[](std::size_t n, const std::nothrow_t&) noexcept { // replacing version
if (n == 0)
++n;
auto p = std::malloc(n);
std::puts("operator new[](size_t, nothrow_t)");
return p;
}

void operator delete(void* p) noexcept { // replacing version
std::puts("operator delete(void*)");
std::free(p);
}

void operator delete(void* p, std::size_t) noexcept { // replacing version
std::puts("operator delete(void*, size_t)");
std::free(p);
}

void operator delete(void* p, const std::nothrow_t&) noexcept { // replacing version
std::puts("operator delete(void*, nothrow_t)");
std::free(p);
}

void operator delete[](void* p) noexcept { // replacing version
std::puts("operator delete[](void*)");
std::free(p);
}

void operator delete[](void* p, std::size_t) noexcept { // replacing version
std::puts("operator delete[](void*, size_t)");
std::free(p);
}

void operator delete[](void* p, const std::nothrow_t&) noexcept { // replacing version
std::puts("operator delete[](void*, nothrow_t)");
std::free(p);
}

int main() {
try {
f();
} catch(const ThrowingDefaultCtor::UniqueException&) {
}
try {
g();
} catch(const ThrowingDefaultCtor::UniqueException&) {
}
}
```

Contributor guide

Open the contributing guide

Research direction

Start with the reduced example in the issue, especially functions f() and g(), and compare Clang's behavior with GCC and MSVC using the linked Godbolt case. Read the [expr.new]/29 wording and trace Clang's overload-resolution handling for deallocation after construction failure; done means the example no longer reports ambiguity and calls the corresponding deallocation functions.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp
Domain
compilers
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.