Can class-specific deallocation functions be used in constant expressions?

139 Views Asked by At

Can I define one of class-specific deallocation functions and use it in a constant expression?

For example a destroying delete:

#include <new>

struct A {
    constexpr void operator delete(A *, std::destroying_delete_t);
};

struct B : A {};

constexpr void A::operator delete(A *p, std::destroying_delete_t) {
    ::delete static_cast<B*>(p);
}

constexpr bool foo() {
    A *p = new B;
    delete p;
    return true;
}

static_assert( foo() );

GCC and MSVC do accept it, however Clang complains:

error: constexpr function never produces a constant expression [-Winvalid-constexpr]
note: call to class-specific 'operator delete'

Online demo: https://godbolt.org/z/47h1Y4sr3

Which behavior is correct?

1

There are 1 best solutions below

0
ecatmur On BEST ANSWER

[expr.const]/5, bullet points 18-19 state that constant evaluation may not encounter:

  • a new-expression ([expr.new]), unless the selected allocation function is a replaceable global allocation function ([new.delete.single], [new.delete.array]) and the allocated storage is deallocated within the evaluation of E;
  • a delete-expression ([expr.delete]), unless it deallocates a region of storage allocated within the evaluation of E;

This does not specify that the selected deallocation function must be a replaceable global deallocation function; as long as it ends up calling such a deallocation function if necessary to satisfy the preceding bullet point, your code is valid.

A further possibly-motivating example:

#include <new>
struct A {
    int& r;
    constexpr void operator delete(A* p, std::destroying_delete_t) {
        p->r = 1;
        ::delete(p);
    }
};
constexpr int foo() {
    int i = 0;
    delete new A{i};
    return i;
}
static_assert(foo() == 1);

An interesting question is raised by the following:

#include <new>
struct B {
    int** q;
    constexpr void operator delete(B* p, std::destroying_delete_t) {
        **p->q = 1;
        delete p->q;
    }
};
constexpr int bar() {
    int j = 0;
    B b{new int*(&j)};
    delete &b;
    return j;
}
static_assert(bar() == 1);

That is, does it matter which region of storage the delete-expression deallocates, or is it expected to be the region of storage corresponding to the object to which the operand of the delete-expression points? Perhaps the current language should be replaced by its inversion:

  • a delete-expression ([expr.delete]), unless it which deallocates a region of storage not allocated within the evaluation of E;