当传入特化类型值时,CTAD是否总会触发NTTP构造?
C++类模板非类型模板参数的构造函数调用争议
代码示例
template<int N> struct Bar { int n = N; constexpr Bar() {} constexpr Bar(const Bar& b): n{b.n - 1} {} }; template<Bar b> constexpr int get_n() { return b.n; } constexpr auto b = Bar<1>{}; static_assert(b.n == 1); #ifdef _MSC_VER static_assert(get_n<b>() == 1); #else static_assert(get_n<b>() == 0); #endif
现象描述
上述代码中,GCC和Clang编译时get_n<b>()返回0,而MSVC返回1。差异源于:当把constexpr变量b(类型为Bar<1>)作为非类型模板参数传递给get_n时,前者会调用Bar的构造函数生成新的模板实参对象,后者直接复用原对象的值。
标准规定分析
类模板内部的名称解析
在类模板Bar<N>的成员函数中,未限定的Bar是类模板名,而非当前特化Bar<N>。因此代码中定义的constexpr Bar(const Bar& b)并非Bar<N>的拷贝构造函数,而是一个泛型构造函数,可接受任意Bar<M>类型的对象。非类型模板参数的转换规则
根据C++标准,当非类型模板参数为类类型时,实参需转换为形参类型:- 若实参与形参类型完全匹配,且存在符合标准定义的拷贝构造函数(参数必须是当前特化的引用),应执行身份转换;但代码中
Bar<N>没有合法的拷贝构造函数,因此编译器会调用那个泛型构造函数生成模板实参对象,导致n的值被减1。 - GCC和Clang严格遵循这一规则,而MSVC错误地将类模板内部的
Bar解析为当前特化,把泛型构造函数当作拷贝构造函数,跳过了构造调用,直接复用原对象的值。
- 若实参与形参类型完全匹配,且存在符合标准定义的拷贝构造函数(参数必须是当前特化的引用),应执行身份转换;但代码中
综上,GCC和Clang的行为符合C++标准,MSVC的行为存在偏差。
内容的提问来源于stack exchange,提问作者VainMan
相关产品推荐
相关产品推荐

