在Clang编译器中,为何模板默认参数会触发析构函数的实例化?
Clang 12.0.1编译失败但GCC/VS通过的模板析构函数实例化问题
咱们先看这段在不同编译器表现不一致的代码:
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;
问题现象
这段代码可以在GCC 11.2和Visual Studio 19.29中正常编译,但在Clang 12.0.1中会编译失败。
问题核心
当函数bar的默认参数5.0调用了constexpr的转发构造函数时,Clang会尝试实例化problem_type<incomplete_type>的析构函数。由于模板参数T是不完全类型(incomplete_type只做了声明,没有定义),T::foo()的调用无法解析,最终导致编译错误。
避免问题的三种情况
只要满足以下任一条件,Clang就不会触发这个错误:
- 默认参数不调用转发构造函数(比如把
5.0改成5,直接调用int版本的构造函数) - 转发构造函数去掉
constexpr修饰 - 把
incomplete_type改为完整类型(比如打开#if 0的注释)
额外补充
- 即使构造函数不是转发构造函数,只要析构函数是
constexpr,这个问题依然会出现 noexcept关键字对这个问题没有影响,代码里保留它只是为了确保编译器不会生成栈展开代码
关键疑问解答:为什么bar既未定义也未被调用,Clang仍报错?
这涉及到C++标准中关于默认参数求值和模板实例化的细节。当处理constexpr构造函数的默认参数初始化时,Clang的实现会要求验证这个初始化表达式是否符合常量表达式的要求。而要验证这一点,编译器需要确保对象的构造和析构过程都是合法的——这意味着它会触发problem_type类的完整实例化,包括析构函数。即使bar没有被调用也没有定义,默认参数是函数声明的一部分,编译器在处理函数声明时就需要对默认参数的合法性进行检查,进而触发了模板的实例化和析构函数的检查。
内容的提问来源于stack exchange,提问作者Stack Exchange Broke The Law
相关产品推荐
相关产品推荐

