C++显式指定部分模板参数时剩余实参推导失败问题
底层成因
这个现象是C标准中类模板实参推导(CTAD,C17引入)的明确规则决定的,和编译器实现无关:
- 类模板实参推导只有在**类名后完全不提供模板实参列表(即不写尖括号)**时才会触发。此时编译器会根据构造调用的传入参数,自动推导所有模板实参,这也是第一段代码可以正常编译的原因——写
DummyBase{"f"}时没有跟尖括号,CTAD正常触发,编译器通过传入的字符串字面量(类型为const char[2])成功推导出a_size=2。 - 只要你在类名后写了尖括号、显式提供了任意数量的模板实参,CTAD就会完全失效。此时编译器只会严格校验你传入的实参数量和模板参数是否匹配(考虑默认模板实参的情况),不会再尝试推导任何未显式指定的模板参数。第二段代码写了
DummyBase<1>{"f"},尖括号里只传了1个实参,但模板总共需要2个参数,又没有默认实参兜底,编译器就会直接报“模板实参数量不足”,根本不会走到根据构造函数参数推导a_size的流程。这个逻辑和标准库std::pair的行为完全一致:你可以写std::pair{1, 2.0}触发CTAD自动推导为std::pair<int, double>,但写std::pair<int>{1, 2.0}就会直接报实参不足,编译器不会自动推导第二个模板参数。 - 调换模板参数顺序后依然编译失败,也是这个规则导致的——和参数顺序无关,只要显式写了部分实参,CTAD就不会启动。
注意:“显式指定部分模板实参、剩余部分自动推导”的逻辑只适用于函数模板,类模板的CTAD从设计之初就不支持部分指定的推导场景,这也是很多人容易混淆的规则点。
可行修复方案
方案1:使用工厂辅助函数(通用性最好,兼容C++17及以上标准)
利用函数模板支持“显式指定部分实参、剩余实参通过传入参数自动推导”的特性,包装一层构造逻辑即可,示例代码:
template<int useless, size_t a_size> class DummyBase { public: DummyBase() = delete; constexpr DummyBase(const char (&i)[a_size]) { } }; // 工厂函数,只需要显式指定useless参数,a_size自动推导 template<int useless, size_t a_size> constexpr auto makeDummy(const char (&i)[a_size]) { return DummyBase<useless, a_size>{i}; } // 调用方式 constexpr const auto dummy = makeDummy<1>("f");
如果需要兼容C++11,只需要去掉对应场景下不支持的constexpr修饰即可正常使用。
方案2:调整模板参数顺序+默认实参(适用于不需要频繁修改非推导参数值的场景)
把需要推导的模板参数放在参数列表最前面,不需要推导的参数放在后面并提供默认值,这样既可以用CTAD做全推导,也可以在需要的时候显式指定所有参数:
// 把需要推导的a_size放前面,固定参数useless放后面给默认值 template<size_t a_size, int useless = 0> class DummyBase { public: DummyBase() = delete; constexpr DummyBase(const char (&i)[a_size]) { } }; // 全推导场景,useless走默认值0 constexpr const auto dummy_default = DummyBase{"f"}; // 需要指定useless时必须手动写全两个实参,不能只写一个 constexpr const auto dummy_spec = DummyBase<2, 1>{"f"};
这个方案的缺点是要指定非默认参数时必须手动填所有实参,无法自动推导a_size,灵活性不如工厂函数方案。
内容的提问来源于stack exchange,提问作者ShadowbladeXI
相关产品推荐
相关产品推荐

