CTAD传递显式参数时实例化其他特化的标准合规性问询
C++标准对NTTP模板参数推导场景的规定疑问
代码示例
#ifdef WITH_CONCEPT # include <concepts> # define CONCEPT std::same_as<unsigned> #else # define CONCEPT #endif template<unsigned...Ns> //requires(sizeof...(Ns) > 0) // workaround struct Foo { static_assert(sizeof...(Ns) > 0); static constexpr auto cnt = sizeof...(Ns); static constexpr auto cnt0 = (0u + ... + (Ns == 0)); constexpr Foo() requires(cnt0 == 0) {} constexpr explicit(cnt0 == 1) Foo(CONCEPT auto...args) requires(cnt0 > 0 && cnt0 == sizeof...(args)) {} }; template<Foo> void func() {} int main() { // 这里没问题,但如果把auto换成Foo会出现同样问题 constexpr auto F = Foo<1>{}; // 部分编译器会实例化Foo<>,触发static_assert断言失败 func<F>(); }
编译器行为测试结果
| 未开启WITH_CONCEPT | 开启WITH_CONCEPT | |
|---|---|---|
| Clang | ❎ | ❎ |
| GCC | ❎ | ✅ |
| MSVC | ✅ | ✅ |
问题描述
调用func<F>()时,部分编译器会实例化Foo<>并触发static_assert(sizeof...(Ns) > 0)断言失败,且不同编译器在开启/关闭WITH_CONCEPT宏时表现存在差异,现咨询该场景下C++标准的具体规定。
补充说明:该问题已简化为Stack Overflow上的相关提问。
Clang编译错误输出
<source>:11:19: error: static assertion failed due to requirement 'sizeof...(Ns) > 0' 11 | static_assert(sizeof...(Ns) > 0); | ^~~~~~~~~~~~~~~~~ <source>:18:24: note: in instantiation of template class 'Foo<>' requested here 18 | constexpr explicit(cnt0 == 1) | ^ <source>:31:10: note: while substituting deduced template arguments into function template '<deduction guide for Foo>' [with Ns = (no value), args:auto = <Foo<1>>] 31 | func<F>(); | ^ <source>:31:5: note: while substituting explicitly-specified template arguments into function template 'func' 31 | func<F>(); | ^ <source>:11:33: note: expression evaluates to '0 > 0' 11 | static_assert(sizeof...(Ns) > 0); | ~~~~~~~~~~~~~~^~~ 1 error generated.
标准规定解析
根据C++20及后续版本的标准,该场景的核心规则如下:
- 模板实参推导的上下文限制:对于
template<Foo> void func() {},当传递Foo<1>作为实参时,编译器应直接匹配Foo<unsigned...>的特化Foo<1>,无需尝试推导无关特化(如Foo<>)。仅当模板实参未显式指定时,才需要触发类模板推导(CTAD)。 - 约束检查优先于实例化:标准明确要求模板的约束检查必须在模板实例化之前进行。开启
WITH_CONCEPT后,CONCEPT auto...args限制参数必须为unsigned类型,而Foo<1>并非该类型,约束直接不满足,编译器可直接排除该推导路径,无需实例化Foo<>——这也是GCC和MSVC开启概念后行为正确的原因。 - 模板实例化的必要性:类模板特化仅在被程序“需要”时才会被实例化。本场景中
Foo<>并非程序实际使用的特化,因此编译器不应实例化它,Clang的行为违反了这一原则。
综上,正确的编译行为应为:编译器直接匹配Foo<1>作为func的模板实参,程序编译通过,Clang的错误行为不符合C++标准规定。
内容的提问来源于stack exchange,提问作者VainMan
相关产品推荐
相关产品推荐

