13

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?

Fedor
  • 17,146
  • 13
  • 40
  • 131
  • [Standard C and C++ do not allow zero-length arrays](https://stackoverflow.com/q/9722632/2402272). GCC and G++ allow them as an extension. – John Bollinger May 22 '23 at 15:55
  • 1
    @JohnBollinger No, `new char[0]` is valid standard C++ while `char arr[0];` is not. – Jason May 22 '23 at 16:01
  • 1
    @JohnBollinger See [C++ new int[0\] -- will it allocate memory?](https://stackoverflow.com/questions/1087042/c-new-int0-will-it-allocate-memory) – Jason May 22 '23 at 16:04
  • @Jason, `new char[0]` does not declare a zero-length array. But you're right that my previous comment is not directly relevant to the question. – John Bollinger May 22 '23 at 16:04
  • Looks like *ill-formed no diagnostic required* to me. – Jason May 22 '23 at 16:11
  • 1
    Here is a [reduced example](https://godbolt.org/z/bWcPoYxjr). So the concept part can be removed from the question. – Jason May 22 '23 at 16:21
  • @Jason ... and changing it to `auto ptr = new A[0]{};` makes clang ok with it too, like OP observed. – Ted Lyngmo May 22 '23 at 16:23
  • Standard verbiage aside, I think it's safe to say GCC's behavior makes the most sense here. – StoryTeller - Unslander Monica May 22 '23 at 16:26
  • @TedLyngmo Yes, so concept part isn't needed in the question to see the observed behavior. – Jason May 22 '23 at 16:27
  • @Jason Probably not. I meant the comment as a confirmation of your observation anyway. – Ted Lyngmo May 22 '23 at 16:34
  • @TedLyngmo I see. Btw, upon reading more, I think the program is well-formed as the standard says: *"allocation function is called to allocate an **array with no elements**."* So the intention seems that when there isn't any element in the array, there is no need of any initialization and so no need for the constructor to be public. Though the standard should probably clear this up by adding some more explanation into the document i.e clarify if this should be the interpretation. – Jason May 22 '23 at 16:39
  • @Jason Yes, unless there's a paragraph explaining why adding `{}` makes it ok but leaving it out is not, gcc seems to be correct like StoryTeller said. – Ted Lyngmo May 22 '23 at 16:46
  • Syntactically the `N` in `new T[N]` is not a _constant-expression_ or some other context in which a constant expression is required. Furthermore, I don't see any special casing for when the expression in the brackets _is_ a (converted) constant expression in [\[expr.new\]](https://timsong-cpp.github.io/cppwp/n4868/expr.new). Therefore, I don't see how the necessity for overload resolution or accessibility checks can depend on the actual value of the expression `N`. – user17732522 May 22 '23 at 18:49
  • @user17732522 I don't think anyone's said that `N` needs to be a constant expression in `new T[N]` - or I may have missed it. Whom are you addressing? – Ted Lyngmo May 22 '23 at 19:05
  • @TedLyngmo Yes, exactly. For that reason a syntactical check that happens at compile-time shouldn't depend on the value of the expression. I am addressing nobody in particular here. I just felt it wasn't formal enough for an answer. – user17732522 May 22 '23 at 19:20
  • @user17732522 Aha, I see what you mean. Good point and thanks for clearing that up! – Ted Lyngmo May 22 '23 at 19:22
  • 1
    I believe it is only fair to mention to SO users that you also opened a [bug report on this issue](https://github.com/llvm/llvm-project/issues/62854) and it would be helpful in the bug report to specify that you also asked the question on SO. This allows for better information sharing. – Shafik Yaghmour May 23 '23 at 19:20
  • @ShafikYaghmour, thanks, indeed. I just added a reference to this discussion in LLVM bug report. – Fedor May 24 '23 at 07:22

2 Answers2

4

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.

duck
  • 1,455
  • 2
  • 8
2

I think that gcc is correct in accepting the program here according to the most sensible interpretation of the wording in expr.new:

When the value of the expression is zero, the allocation function is called to allocate an array with no elements.

(emphasis mine)

The intention of the above clause seems to be that since the array has no elements, there is no need for any initialization and hence no need for the constructor to be accessible or even exist.


I also think this clause in the standard could be made more clear by adding something like "In this case there is no need for the ctor to be accessible...."

Jason
  • 36,170
  • 5
  • 26
  • 60
  • What's the application?! Is the result `nullptr` or pointer to uninitialized memory? Both cases lead to UB. Either the return from underlying function is never `free`d or the returned pointer will be used as a proper object. – Red.Wave May 22 '23 at 17:07
  • 3
    @Red.Wave It will not return `nullptr` in C++ and there's no UB if it's not dereferenced. A motivation for why it's possible can be found in https://stackoverflow.com/a/1087066/7582247 _"The intent is to have operator new() implementable by calling malloc() or calloc(), so the rules are substantially the same. C++ differs from C in requiring a zero request to return a non-null pointer."_ – Ted Lyngmo May 22 '23 at 17:09
  • The `N` is not required to be a constant expression or is special cased for constant expressions. So at best you could say that it is unspecified whether or not the constructor overload resolution and accessibility check are done. Nothing I see in [expr.new] is requiring the compiler to diagnose whether the size is a constant expression. – user17732522 May 22 '23 at 18:51
  • @TedLyngmo Addressing the answer's author here. The answer suggest that the compiler _ought_ to not check the overload resolution and accessibility in this case, but that would require the compiler to test that the runtime expression is a constant expression, which I do not see as reasonable if nothing in the standard's wording specifically makes that distinction. – user17732522 May 22 '23 at 19:22
  • @user17732522 Oups, I removed my comment before you answered :) ... but thanks for this too. – Ted Lyngmo May 22 '23 at 19:23