析构函数必须仅可用(公共)还是对默认初始化的类成员完全有效?

Fed*_*dor 5 c++ language-lawyer incomplete-type c++20

请考虑一个A具有带默认初始值设定项的类型字段u的结构U<R>。析构函数~U<R>仅声明:

template<typename T>
struct U {
    ~U();
};

struct R;

struct A {
    U<R> u = U<R>{};
};
Run Code Online (Sandbox Code Playgroud)

所有编译器都接受此代码,演示: https: //gcc.godbolt.org/z/oqMjTovMo

但是如果我们将析构函数定义~U<R>如下:

template<typename T>
struct U {
    ~U() { static_assert( sizeof(T) > 0 ); }
};

Run Code Online (Sandbox Code Playgroud)

那么当前的编译器就会出现分歧。MSVC 不断接受程序,而 GCC/Clang 打印错误

error: invalid application of 'sizeof' to an incomplete type 'R'
Run Code Online (Sandbox Code Playgroud)

演示: https: //gcc.godbolt.org/z/713TzPd6v

显然,编译器必须验证默认初始化类成员的析构函数的可用性,以防在构造过程中发生异常。但标准是否要求编译器只检查析构函数的可用性(如 MSVC 所做的那样),还是编译器也应该验证其主体?RMSVC 行为在这里看起来更方便,因为它允许在结构定义时进行前向声明A

PS:这个探索不仅仅具有纯粹的理论意义。如果将U此处替换为std::unique_ptrthen ,则可以解释为什么类型的类字段std::unique_ptr<R>被 MSVC 接受并被 GCC/Clang 拒绝为不完整的类R

rus*_*tyx 2

[dcl.init.aggr]/8:(强调我的)

类类型的每个元素的析构函数都可能从发生聚合初始化的上下文中调用([class.dtor])。

[basic.def.odr]/8

如果类的析构函数可能被调用,则它是odr-used 的

[基本.def.odr]/10 :

每个程序都应包含在该程序中在废弃语句之外使用的每个非内联函数或变量的精确定义;无需诊断。

So~U()可能在U<R> uinside调用A,这需要它的定义。

然后根据[temp.inst]/4

除非类模板或成员模板的成员是已声明的特化,否则当在需要成员定义存在的上下文中引用特化时,会隐式实例化该成员的特...

额外说明:当然,每当 的实例被销毁时,它实际上都会被调用A。所以需要编译。

注意:在第一个版本中,编译器接受代码,因为析构函数不是内联的,因此它在链接期间失败(示例)。

至于MSVC接受代码,这似乎是一个错误。