Search code examples
c++constructorlanguage-lawyernew-operator

new A[0]: Initialization of zero-size array with dynamic storage duration for a class with inaccessible constructor


I have a C++ concept for checking that an array of objects can be dynamically allocated:

template< class T, int N >
concept heap_constructible = requires() {
    delete[] new T[N];
};

And I found accidently that compilers diverge in its evaluation in case of zero-size arrays N=0 and inaccessible (e.g. private:) constructor:

class A {
    A();
};

static_assert( !heap_constructible<A, 5> ); // OK everywhere
static_assert( heap_constructible<A, 0> );  // OK in GCC only

Only GCC seems to allow zero-size allocation of A-objects. Clang prints the error:

calling a private constructor of class 'A'

In case of slight modification of the concept:

template< class T, int N >
concept heap_constructible1 = requires() {
    delete[] new T[N]{}; // note additional empty braced list
};

Clang accepts it as well:

static_assert( heap_constructible1<A, 0> ); // OK in GCC and Clang

but not MSVC, which evaluates both heap_constructible<A, 0> and heap_constructible1<A, 0> to false. Online demo: https://gcc.godbolt.org/z/nYr88avM4

Which compiler is right here?


Solution

  • Use of constructors by a new-expression is currently underspecified; this is CWG2102 (originating from this Clang issue).


    The array bound in a new-expression does not have to be a constant expression, and when it is not, there's no way to tell until runtime whether the new-initializer covers every element of the array or if extra initialization will need to be done for the trailing elements. This means that, in general, the element type needs to be default-constructible.

    However, when the array bound is a constant expression, this requirement seems superfluous. And indeed, all major implementations accept code like #1 in the following example (while rejecting #2):

    struct S {
        S() = delete;
        S(int);
    };
    
    const int a = 3;
    S* p = new S[a] {1, 2, 3}; // #1
    
    int b = 3;
    S* q = new S[b] {1, 2, 3}; // #2
    

    But the standard does not make a distinction between the two cases.


    With that in mind, I'd say that GCC's behavior is the most consistent here: neither default- ([dcl.init.general]/7.2) nor aggregate ([dcl.init.aggr]) initialization of a zero-sized array of T use any of T's constructors, so if new S[3] {1, 2, 3} in the above example is OK, new S[0] should also be fine.