含虚函数的类型用作非类型模板参数时GCC编译失败,是否符合C++标准或为GCC Bug?
这确实是GCC的实现Bug,而非你的写法违反了C++标准——毕竟Clang、MSVC和Intel编译器都能正常编译你的代码,这已经能说明问题了。咱们来仔细拆解下:
1. 先明确C++标准的允许范围
C++标准对**非类型模板参数(NTTP)**的类类型有两个核心要求:
- 该类必须是字面类型(literal type)
- 用于模板参数的对象必须是常量表达式初始化的
对于带虚函数的类,标准并没有禁止它成为字面类型:只要类拥有constexpr构造函数、constexpr析构函数,且所有非静态数据成员都是字面类型,就算有虚函数也完全符合字面类型的定义。你的string_literal结构体完全满足这些条件:
- 有
constexpr构造函数和析构函数 - 成员
chars是std::array,属于字面类型
所以从标准层面,你的写法是完全合法的。
2. GCC错误的根源
看GCC给出的错误信息:
error: '((& string_literal<12, const char16_t>::_ZTV14string_literalILm12EKDsE) + 16)' is not a valid template argument for 'int (**)(...)' because it is not the address of a variable
这说明GCC在处理带虚函数的constexpr对象时,错误地把虚函数表(vtable)的地址当成了模板参数的一部分。但在常量表达式语境下,带虚函数的字面类型对象的初始化应该是被允许的,虚函数表的引用不会影响对象作为模板参数的合法性——其他编译器都正确处理了这一点,只有GCC在这里出现了逻辑错误。
你给出的简化示例也能验证这一点:
class Foo{ virtual void Bar(){ } }; template<Foo foo> void XXX(){ } int main(){ constexpr static auto foo = Foo(); XXX<foo>(); return 0; }
这段代码同样只有GCC编译失败,进一步坐实了是GCC的实现问题。
3. 结论
你的用法完全符合C++标准,这次编译失败是GCC的Bug导致的。这类Bug通常和GCC对带虚函数的字面类型在常量表达式语境下的处理逻辑有关,尤其是模板参数推导阶段对虚函数表的处理有误。你可以去GCC的Bugzilla平台搜索相关问题,大概率已经有类似的报告,也可以补充你的案例来推动修复。
内容来源于stack exchange

