struct incomplete_type;
#if 0
struct incomplete_type {
static void foo() {}
};
#endif
template<typename T>
struct problem_type
{
constexpr problem_type(int) noexcept {};
constexpr problem_type(double) noexcept : problem_type(5) {}
~problem_type() {
T::foo();
}
};
void bar(problem_type<incomplete_type> arg=5.0) noexcept;
当bar
有一个调用转发构造函数(也是constexpr
)的默认参数时,编译器也会尝试实例化析构函数,但失败,因为T::foo
是不完整的类型,所以无法调用。
如果默认参数未调用转发构造函数(例如,如果我们将5.0
更改为5
),如果转发构造函数不是constexpr
,或者(当然)如果类型T
此时已完成,则不会出现问题。
如果析构函数是constexpr
,即使构造函数没有转发,也会出现问题。
看起来没有关系,但是我保留了它以确保编译器不会尝试生成堆栈展开代码。
这只发生在clang(12.0.1)上,而不是gcc(11.2)或Visual Studio(19.29)上。见导栓
请注意,未定义或调用函数bar
。
为什么这不是在clang编译?
首先请注意,Clang在这里更为一致,因为GCC和Clang都拒绝使用类字段默认初始化的类似示例,其中析构函数体仍然引用不完整类型:
struct incomplete_type;
template<typename T>
struct problem_type
{
constexpr problem_type(int) noexcept {};
constexpr problem_type(double) noexcept : problem_type(5) {}
constexpr ~problem_type() {
T::foo();
}
};
// ok in all compilers
struct A {
problem_type<incomplete_type> p;
};
// error in GCC and Clang
struct B {
problem_type<incomplete_type> p = 5.0;
};
演示:https://gcc.godbolt.org/z/59hW3ecex
在您的示例中,problem\u类型的析构函数
要使您的示例被所有编译器接受,请在函数bar
之后定义类主体之外的析构函数~problem_type
:
struct incomplete_type;
template<typename T>
struct problem_type
{
constexpr problem_type(int) noexcept {};
constexpr problem_type(double) noexcept : problem_type(5) {}
constexpr ~problem_type();
};
void bar(problem_type<incomplete_type> arg=5.0) noexcept;
template<typename T>
constexpr problem_type<T>::~problem_type() {
T::foo();
}
演示:https://gcc.godbolt.org/z/rvzMbbz9d
但是您仍然需要在调用bar
函数的地方将complete\u type
变为complete。