提问者:小点点

在clang中,为什么这个模板默认参数要求实例化析构函数?


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编译?


共1个答案

匿名用户

首先请注意,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。