英文:
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 accidentally 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?
英文:
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?
答案1
得分: 4
以下是翻译好的部分:
"Use of constructors by a new-expression is currently underspecified; this is CWG2102 (originating from this Clang issue)."
"在 new-expression 中使用构造函数目前规范不明确;这是 CWG2102 (源自 此 Clang 问题)。"
"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."
"new-expression 中的数组边界不必是常量表达式,当不是常量表达式时,无法在运行时确定 new-initializer 是否涵盖数组的每个元素,或者是否需要对尾随元素进行额外初始化。这意味着一般情况下元素类型需要具备默认构造能力。"
"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
):"
"然而,当数组边界是常量表达式时,这个要求似乎是多余的。事实上,所有主要的实现都接受以下示例中的类似 #1
的代码(而拒绝 #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
"但标准没有区分这两种情况。"
"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."
"考虑到这一点,我认为GCC的行为在这里是最一致的:对于大小为零的数组 T
,既不使用 T
的默认构造函数([dcl.init.general]/7.2),也不使用聚合初始化([dcl.init.aggr]),因此如果在上面的示例中 new S[3] {1, 2, 3}
是可以的,new S[0]
也应该是可以的。"
英文:
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.
答案2
得分: 2
我认为根据expr.new中的措辞的最明智解释,gcc在接受该程序时是正确的:
> 当表达式的值为零时,调用分配函数分配一个没有元素的数组。
(重点是我的)
上述条款的意图似乎是因为数组没有元素,所以没有必要进行任何初始化,因此不需要让构造函数可访问甚至存在。
我也认为标准中的这一条款可以通过添加类似“在这种情况下,无需让构造函数可访问...”的内容来更清晰地表达。
英文:
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...."
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论