<html><head><meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>P0722R2: Efficient sized delete for variable sized classes</title>

<style type="text/css">
ins, ins > * { text-decoration:none; font-weight:bold; background-color:#A0FFA0 }
del, del > * { text-decoration:line-through; background-color:#FFA0A0 }

blockquote {
  color: #000000; background-color: #F1F1F1;
  border: 1px solid #D1D1D1;
  padding-left: 0.5em; padding-right: 0.5em;
}
blockquote.stdins {
  color: #000000; background-color: #C8FFC8;
  border: 1px solid #B3EBB3;
  padding-left: 0.5em; padding-right: 0.5em;
}
blockquote.stddel {
  text-decoration: line-through;
  color: #000000; background-color: #FFEBFF;
  border: 1px solid #ECD7EC;
  padding-left: 0.5em; padding-right: 0.5em;
}
</style>

</head>

<body>
Richard Smith, Andrew Hunter<br>
2017-11-27<br>
Audience: Core, Library

<h1>P0722R2: Efficient sized delete for variable sized classes</h1>

This paper provides wording for the feature described by <a href="http://wg21.link/p0722r1">P0722R1</a>.

<p>
Append to [basic.stc.dynamic.deallocation] (6.7.4.2) paragraph 1
</p>

<blockquote>
[&hellip;] or declared in global scope.
<ins>
A deallocation function with at least two parameters
whose second parameter type is of type <tt>std::destroying_delete_t</tt>
is a <i>destroying operator delete</i>.
A destroying operator delete shall be a class member function named
<tt>operator delete</tt>.
</ins>
</blockquote>

<p>
Change in [basic.stc.dynamic.deallocation] (6.7.4.2) paragraph 2:
</p>

<blockquote>
Each deallocation function shall return <tt>void</tt><ins>.
If the function is a destroying operator delete declared in class type <tt>C</tt>, the type
of its first parameter shall be <tt>C*</tt>; otherwise, the type of</ins>
<del>and</del> its first parameter shall be <tt>void*</tt>.
A deallocation function may have more than one parameter.
A <i>usual deallocation function</i> is a deallocation function <del>that has:</del>
<ins>whose parameters after the first are</ins>
<ul>
<li><del>exactly one parameter; or</del> <ins>optionally, a parameter of type <tt>std::destroying_delete_t</tt>, then
<li><del>exactly two parameters, the type of the second being either <tt>std::align_val_t</tt> or</del> <ins>optionally, a parameter of type <tt>std::size_t</tt></ins>[Footnote: The global <tt>operator delete(void*, std::size_t)</tt> precludes use of an allocation function <tt>void operator new(std::size_t, std::size_t)</tt> as a placement allocation function]<del>; or</del><ins>, then<ins>
<li><del>exactly three parameters, the type of the second being <tt>std::size_t</tt> and the type of the third being</del> <ins>optionally, a parameter of type</ins> <tt>std::align_val_t</tt>.
</ul>
<ins>A destroying operator delete shall be a usual deallocation function.</ins>
A deallocation function may be an instance of a function template.
Neither the first parameter nor the return type shall depend on a template parameter.
[ Note: That is, a deallocation function template shall have a first parameter of type <tt>void*</tt>
and a return type of <tt>void</tt> (as specified above). &mdash; end note ]
A deallocation function template shall have two or more function parameters.
A template instance is never a usual deallocation function, regardless of its signature.
</blockquote>

<p>
Change in [expr.delete] (8.3.5) paragraph 3:
</p>

<blockquote>
In the first alternative (delete object),
if the static type of the object to be deleted is different from its dynamic type,
<ins>and the selected deallocation function (see below) is not a destroying operator delete,</ins>
the static type shall be a base class of
the dynamic type of the object to be deleted and
the static type shall have a virtual destructor
or the behavior is undefined.
In the second alternative (delete array)
if the dynamic type of the object to be deleted
differs from its static type, the behavior is undefined.
</blockquote>

<p>
Change in [expr.delete] (8.3.5) paragraph 8:
</p>

<blockquote>
If the value of the operand of the <i>delete-expression</i>
is not a null pointer value,
<ins>and the selected deallocation function (see below)
  is not a destroying operator delete,</ins>
the <i>delete-expression</i> will invoke the destructor (if any)
for the object or the elements of the array being deleted.
In the case of an array,
the elements will be destroyed in order of decreasing address
(that is, in reverse order of the completion of their constructor; see 15.6.2).
</blockquote>

<p>
Change in [expr.delete] (8.3.5) paragraph 10:
</p>

<blockquote>
If deallocation function lookup finds more than one usual deallocation
function, the function to be called is selected as follows:

<ul>
<li><ins>If any of the deallocation functions is a destroying operator delete,
all deallocation functions that are not destroying operator deletes are
eliminated from further consideration.</ins>
<li>If the type has new-extended alignment, a function with a parameter of type
<tt>std::align_val_t</tt> is preferred; otherwise a function without such a parameter
is preferred. If <ins>any</ins> <del>exactly one preferred function is found, that function is
selected and the selection process terminates. If more than one</del> preferred
function<ins>s are</ins> <del>is</del> found,
all non-preferred functions are eliminated from further consideration.
<li><ins>If exactly one function remains, that function is selected and
    the selection process terminates.</ins>
<li>If the deallocation functions have class scope, the one without a parameter
  of type <tt>std::size_t</tt> is selected.
<li>If the type is complete and if, for the second alternative (delete array)
  only, the operand is a pointer to a class type with a non-trivial destructor
  or a (possibly multi-dimensional) array thereof, the function with a
  parameter of type <tt>std::size_t</tt> is selected.
<li>Otherwise, it is unspecified whether a deallocation function with a
  parameter of type <tt>std::size_t</tt> is selected.
</ul>
</blockquote>

<em>Drafting note: the pre-existing wording makes the incorrect assumption that
(after the alignment step) there must be one sized operator delete and one
non-sized operator delete. That appears to be false: we could have (e.g.) both
<tt>operator delete(void*)</tt> and <tt>operator delete(void*, ...)</tt> here
(both are usual deallocation functions).</em>

<p>
Change in [expr.delete] (8.3.5) paragraph 11:
</p>

<blockquote>
<ins>For the delete object case, the deleted object is the object denoted
by the operand if its static type does not have a virtual destructor,
and its most-derived object otherwise.
[ Note: If the deallocation function is not a destroying operator delete
and the deleted object is not the most derived object in the former case,
the behavior is undefined, as stated above. &mdash; ]
For the delete array case, the deleted object is the array object.
</ins>
When a delete-expression is executed, the selected deallocation function shall
be called with the address of the <del>most-derived</del> <ins>deleted</ins> object
in the delete object case,
or the address of the <ins>deleted</ins> object suitably adjusted for the array allocation
overhead (8.3.4) in the delete array case, as its first argument.
<ins>If a destroying operator delete is used, an unspecified value is passed
as the argument corresponding to the parameter of type
<tt>std::destroying_delete_t</tt>.</ins>
If a deallocation function with a parameter of type <tt>std::align_val_t</tt>
is used, the alignment of the type of the <ins>deleted</ins> object
<del>to be deleted</del> is passed as the corresponding argument.
If a deallocation function with a parameter of type
<tt>std::size_t</tt> is used, the size of the <del>most-derived type</del>
<ins>deleted object in the delete object case</ins>, or of the array plus
allocation overhead<del>, respectively</del> <ins>in the delete array case</ins>,
is passed as the corresponding argument.
<del>
[ Footnote: If the static type of the object to be deleted is complete and is
different from the dynamic type, and the destructor is not virtual, the size
might be incorrect, but that case is already undefined, as stated above. ]</del>
[ Note: If this results in a call to a <del>usual</del> <ins>replaceable</ins>
deallocation function, and either
the first argument was not the result of a prior call to a <del>usual</del> <ins>replaceable</ins> allocation
function or the second <ins>or third</ins> argument was not the corresponding argument in said
call, the behavior is undefined (21.6.2.1, 21.6.2.2). &mdash; end note ]
</blockquote>

<p>
Change in [expr.delete] (8.3.5) paragraph 12:
</p>

<blockquote>
Access and ambiguity control are done for <del>both</del>
the deallocation function and<ins>,
if
the deallocation function is not a destroying operator delete or
the destructor is virtual, for</ins>
the destructor (15.4, 15.5).
</blockquote>

<p>
Change in [class.free] (15.5) paragraph 4:
</p>

<blockquote>
Class-specific deallocation function lookup is a part of general deallocation
function lookup (8.3.5) and occurs as follows. If the delete-expression is used
to deallocate a class object whose static type has a virtual destructor, the
deallocation function is the one selected at the point of definition of the
dynamic type's virtual destructor (15.4).[Footnote] Otherwise, if the
delete-expression is used to deallocate an object of class T or array thereof,
<del>the static and dynamic types of the object shall be identical and</del> the
deallocation function's name is looked up in the scope of T. If this lookup
fails to find the name, general deallocation function lookup (8.3.5) continues.
If the result of the lookup is ambiguous or inaccessible, or if the lookup
selects a placement deallocation function, the program is ill-formed.
</blockquote>

<p>
Change in [class.free] (15.5) paragraph 6:
</p>

<blockquote>
Since member allocation and deallocation functions are static they cannot be
virtual. [Note: However, when the cast-expression of a delete-expression refers
to an object of class type, because the deallocation function actually called
is looked up in the scope of the class that is the dynamic type of the object<del>,</del>
if the destructor is virtual, the effect is the same <ins>in that case</ins>. For example,
<pre>
struct B {
  virtual ~B();
  void operator delete(void*, std::size_t);
};
struct D : B {
  void operator delete(void*);
};
<ins>struct E : B {
  void operator delete(void*, std::destroying_delete_t);
};</ins>
void f() {
  B* bp = new D;
  delete bp; // 1: uses D::operator delete(void*)
<ins>  bp = new E;
  delete bp; // 2: uses E::operator delete(void*, std::destroying_delete_t)</ins>
}
</pre>
Here, storage for the <del>non-array</del> object of class <tt>D</tt> is deallocated by <tt>D::operator delete()</tt>,
<ins>and the object of class <tt>E</tt> is destroyed and its storage is deallocated by <tt>E::operator delete()</tt>,
due to the virtual destructor. &mdash; end note ]
[ Note: &hellip; ]
</blockquote>

<p>
Change in header &lt;new> synopsis in [new.syn] (21.6.1):
</p>

<blockquote>
<pre>namespace std {
  class bad_alloc;
  class bad_array_new_length;

<ins>  // [new.destr.del], destroying delete indicator
  struct destroying_delete_t { <i>see below</i> };
  inline constexpr destroying_delete_t destroying_delete(<i>unspecified</i>);</ins>

  struct nothrow_t { explicit nothrow_t() = default; };
  [&hellip;]
</pre>
</blockquote>

<p>
Add a new subclause before the existing 21.6.4 [ptr.launder]:
</p>

<blockquote class="stdins">
<h2>Destroying delete indicator [new.destr.del]</h2>

<pre>
struct destroying_delete_t { <i>see below</i> };
inline constexpr destroying_delete_t destroying_delete(<i>unspecified</i>);
</pre>

<p>The struct <tt>destroying_delete_t</tt>
is an empty structure type
used as a unique type
to indicate that a deallocation function is a destroying operator delete ([basic.stc.dynamic.deallocation]).

<p>Type <tt>destroying_delete_t</tt> shall not have a default constructor or an initializer-list constructor,
and shall not be an aggregate.
</blockquote>

</body></html>
