约束含前置非包模板参数时,带模板参数包的函数为何编译失败?
问题:实现支持任意正数量类型的
std::same_as通用版的编译差异 问题场景
我尝试编写一个通用版本的std::same_as,使其支持任意正数量的类型实参。首先写出了以下concept:
template <typename T1, typename... Ts> concept all_same_v1 = (... && std::same_as<T1, Ts>);
但在使用这个concept时,下面的代码在GCC和Clang中无法编译,仅能在MSVC中编译:
template <typename... Ts> requires all_same_v1<Ts...> void f(Ts&&... ts);
GCC给出的错误信息如下:
<source>:6:36: error: pack expansion argument for non-pack parameter 'T1' of concept 'template<class T1, class ... Ts> concept all_same_v1' 6 | template <typename... Ts> requires all_same_v1<Ts...> | ^~~~~~~~~~~~~~~~~~ <source>:3:11: note: declared here 3 | template <typename T1, typename... Ts> | ^~~~~~~~
相比之下,另一个O(N²)复杂度的实现可以在所有编译器中正常工作:
template <typename T, typename... Others> concept same_with_others = (... && std::same_as<T, Others>); template <typename... Types> concept all_same_v2 = (... && same_with_others<Types, Types...>); template <typename... Ts> requires all_same_v2<Ts...> void f(Ts&&... ts);
原因解析
关于all_same_v1的编译错误
all_same_v1的模板参数列表是单个非包参数T1加上包参数Ts...,这意味着它要求调用时必须至少传入1个类型实参(T1的实参),后续实参可选。
但在requires all_same_v1<Ts...>的写法中,Ts...是一个模板参数包:
- 当
Ts...为空包时,all_same_v1<Ts...>等价于all_same_v1<>,直接违反了all_same_v1对T1参数的必填要求; - 即使
Ts...非空,GCC和Clang严格遵循C++标准的模板实参匹配规则:包展开的实参不能直接填充包含非包参数的模板参数列表——编译器在模板声明阶段就会判定这种写法存在规则冲突,无法确定如何将Ts...的展开结果分配给T1和后续的Ts...包参数。
MSVC则采用了更宽松的非标准扩展逻辑,它会尝试将Ts...的第一个元素绑定到T1,剩余元素绑定到all_same_v1的Ts...包参数,因此可以编译通过,但这不属于标准行为。
关于all_same_v2的正确性
all_same_v2的实现通过双重包展开规避了参数匹配问题:
- 外层对
Types...的每个类型T,调用same_with_others<T, Types...>; same_with_others负责检查单个类型T是否与所有Types类型相同。
这种写法的核心优势在于:
same_with_others的模板参数列表是单个非包参数T加包参数Others...,调用时same_with_others<T, Types...>的参数匹配完全符合标准规则:T绑定到第一个参数,Types...绑定到Others...包参数;- 即使
Types...为空包,(... && same_with_others<Types, Types...>)会触发空折叠表达式,结果为true(符合空包“所有类型相同”的逻辑约定); - 所有编译器都认可这种标准的包展开和模板参数匹配方式,因此不存在编译差异。
内容的提问来源于stack exchange,提问作者Bernard
相关产品推荐
相关产品推荐

