Short answer:
As of C++14, global function void operator delete(void*, size_t) noexcept
is defined as a usual (non-placement) delete
function, and thus is no longer considered a placement delete
function (this behaviour matches static
member function void T::operator delete(void*, size_t) noexcept
, which is a usual delete
function). This means it can't match any placement new
functions, causing the error when lookup discovers it while looking for void* PairBlob::operator new(size_t, size_t)
's match.
Adding void PairBlob::operator delete(void*)
prevents lookup from discovering the aforementioned global operator delete()
overload, and thus the placement new
won't call a placement delete
if the constructor throws.
Long answer:
Compare the functions described in [new.delete.single]
in C++11 and C++14 (all information other than delete
function signatures omitted for brevity).
void operator delete(void* ptr) noexcept;
void operator delete(void* ptr, const std::nothrow_t&) noexcept;
void operator delete(void* ptr) noexcept;
void operator delete(void* ptr, std::size_t size) noexcept;
void operator delete(void* ptr, const std::nothrow_t&) noexcept;
void operator delete(void* ptr, std::size_t size, const std::nothrow_t&) noexcept;
Examination of C++14 [new.delete.single/14]
appears to indicate that if present, size
must be the value implicitly passed to the usual void* operator new(size_t)
, when called as new Whatever
.
As specified in [basic.stc.dynamic/2]
, out of these single-object delete
functions, the following are implicitly declared, in global scope, in all translation units:
void operator delete(void*);
void operator delete(void*) noexcept;
void operator delete(void*, std::size_t) noexcept;
Similarly, in C++14, [basic.stc.dynamic.deallocation/2]
contains this clause:
The global operator delete
with exactly two parameters, the second of which has type std::size_t
, is a usual deallocation function.
This clause isn't present in the C++11 version of [basic.stc.dynamic.deallocation/2]
.
Furthermore, footnote 37 in the C++14 version, referring to operator delete(void* ptr, size_t) noexcept
and operator delete[](void* ptr, size_t) noexcept
, states that:
This deallocation function precludes use of an allocation function void operator new(std::size_t, std::size_t)
as a placement allocation function ([diff.cpp11.basic]).
The linked page notes that this change may break valid C++11 code which relies on global function void operator delete(void*, size_t) noexcept
being a placement delete
function, causing the program to be ill-formed when compiled as C++14. This matches the sized delete
function's behaviour as a class member, where code that relies on static
member function void T::operator delete(void*, size_t) noexcept
being a placement delete
function is ill-formed.
In regards to static
member function void T::operator delete(void*, size_t)
, [basic.stc.dynamic.deallocation/2]
states, in both C++11 and C++14, that:
If a class T
has a member deallocation function named operator delete
with exactly one parameter, then that function is a usual deallocation function. If class T
does not declare such an operator delete
but does declare a member deallocation function named operator delete
with exactly two parameters, the second of which has type std::size_t
, then this function is a usual deallocation function.
Quote taken from C++14 version. The C++11 version clarifies that "usual" means "non-placement", and links to [support.types]
for a definition of std::size_t
. The C++14 version clarifies the meaning of "usual" before reaching this clause, and omits the link to [support.types]
.
[expr.new]
(specifically, [expr.new/20]
in C++11, and [expr.new/22]
in C++14) indicates that for a call to any placement new
function, it will try to find a corresponding placement delete
function via lookup, which will be called if the constructor throws when using that placement new
; an operator delete()
will be considered a match if its parameter list matches the operator new()
's parameter list (with the exception of the first parameter, which must always be size_t
for operator new()
and void*
for operator delete()
). As deallocation functions must be either global or class members, name lookup rules state that when matching T::operator new()
, it will fall back to the global operator delete()
s if no member operator delete()
s are found. [See here for a demonstration of this behaviour.]
If a corresponding function is discovered by lookup, and that function is a placement delete
function, all is well and good; if it's not, however, then the program is ill-formed. Therein lies the rub; since PairBlob
doesn't originally contain an operator delete()
, lookup falls back to the global scope, and locates void operator delete(void*, size_t)
(if present). This is a placement delete
function in C++11 or earlier (and may not even be present, in which case see the below paragraph), but an implicitly declared usual delete
function in C++14 or later (and thus always present), meaning that any code which relies on it as a placement delete
becomes ill-formed during the transition from C++11 to C++14.
If no corresponding function is discovered by lookup, however, then no placement delete
function will be called if the constructor throws. This is why adding void PairBlob::operator delete(void*)
removes the error; as lookup stops when it finds this function, the global operator delete(void*, size_t) noexcept
will not be discovered, and thus the program won't become ill-formed by trying to use it as a placement delete
function. [Note, however, that adding void PairBlob::operator delete(void*, size_t)
would cause the same error as relying on the global one, as static void T::operator delete(void*, size_t) noexcept
is also a usual delete function.]
GCC 6 defaults to C++14, which has caused this to occur before. MSVC 2015 and later will also catch this, and emit error C2956; it doesn't appear to have a corresponding MSDN page at the moment, but this bug report explicitly refers to [expr.new/22]
(albeit by its number, § 5.3.4/22, rather than its name). Surprisingly, however, Clang fails to catch it, and will continue to treat void operator delete(void*, size_t) noexcept
as a placement delete
. [Compare them here, if you want.]
As mentioned on the GCC link above, the best solution is to modify the code to be compatible with C++14, but compiling with a previous C++ standard is a viable workaround. Adding a member operator delete()
that prevents name lookup from finding the global one does indeed prevent this problem, but I'm not sure whether it would be better to modify the placement new
instead.