Qt5信号槽编译期参数检查的模板特化匹配规则疑问
问题背景
我正在尝试理解Qt5框架的Signals and Slots机制如何实现函数参数的编译期类型检查。
我已整理出所关注的相关代码片段,目前的核心疑问为:编译器是如何判定并实例化下述模板特化版本的?我尤其无法理解模板参数列表为template< typename Arg1, typename Arg2, typename... Tail1, typename... Tail2 >的特化会在何种场景下被编译器匹配选中,特化选择的具体规则是什么?
核心疑问对应的特化代码如下:
template< typename Arg1, typename Arg2, typename... Tail1, typename... Tail2 > struct Test< List< Arg1, Tail1... >, List< Arg2, Tail2... > > { enum { value = ArgsCompat< Arg1, Arg2 >::value && Test< List< Tail1... >, List< Tail2... > >::value }; };
完整测试代码
namespace Test { template< typename... > struct List{}; template< typename H, typename... T > struct List< H, T... >{ typedef H Head; typedef List< T... > Tail; }; template< typename T > struct RemoveRef { typedef T type; }; template< typename T > struct RemoveRef< T& > { typedef T type; }; template< typename Arg1, typename Arg2 > struct ArgsCompat { static int test( const typename RemoveRef< Arg2 >::type & ); enum { value = true }; }; template< typename Elem1, typename Elem2 > struct Test { enum { value = false}; }; template<> struct Test< List<>, List<> > { enum { value = true }; }; template< typename Arg1> struct Test< Arg1, List<> > { enum { value = true }; }; template< typename Arg1, typename Arg2, typename... Tail1, typename... Tail2 > struct Test< List< Arg1, Tail1... >, List< Arg2, Tail2... > > { enum { value = ArgsCompat< Arg1, Arg2 >::value && Test< List< Tail1... >, List< Tail2... > >::value }; }; } int main( void ) { static_assert( Test::Test< Test::List<>, Test::List<> >::value, "" ); static_assert( Test::Test< Test::List< char >, Test::List< char > >::value, "" ); static_assert( Test::Test< Test::List< char, int, double >, Test::List< char, int, double > >::value, "expected true" ); return 0; }
模板特化匹配规则说明
C++类模板特化匹配的核心规则是:编译器会将实例化时传入的实际参数,和主模板之外所有可用的特化版本做模式匹配,最终选择约束最严格、匹配度最高的版本;如果没有任何特化匹配成功,才会使用主模板生成实例。
代码中Test模板一共有4个版本,各自的匹配场景如下:
- 主模板:是兜底版本,只要传入的两个参数不满足下面所有特化的匹配模式,就会选中该版本,返回
false,代表参数不兼容。 - 空列表全特化版本:仅当两个传入参数都是空类型
List<>时匹配,对应信号和槽的参数列表均为空的场景,直接返回true。 - 槽参数为空的偏特化版本:当第二个传入参数是空
List<>、第一个参数为任意类型时匹配,对应Qt信号槽的合法规则:槽的参数数量可以少于信号的参数数量,多余的信号参数会被自动忽略,因此这种情况直接返回true。 - 带变长参数的偏特化版本(即你疑惑的版本):匹配条件非常明确——两个传入参数必须都是非空的List类型:第一个List至少包含1个元素(该元素被推导为
Arg1,剩余所有元素打包为参数包Tail1),第二个List也至少包含1个元素(该元素被推导为Arg2,剩余所有元素打包为参数包Tail2)。
该变长特化的校验逻辑是典型的编译期递归:首先校验当前位置的一对参数Arg1和Arg2是否满足兼容要求(通过ArgsCompat判断隐式转换合法性),再递归校验剩余参数列表Tail1和Tail2的兼容性,直到递归触达终止条件(空列表匹配、或第二个列表为空的匹配)。
以代码中Test< List<char, int, double>, List<char, int, double> >::value的求值过程为例:
- 两个参数都是非空List,匹配变长特化:校验首参数
char和char兼容通过,剩余参数为List<int, double>和List<int, double>,进入递归。 - 递归传入的两个参数仍为非空List,继续匹配变长特化:校验
int和int兼容通过,剩余参数为List<double>和List<double>,再次递归。 - 当前层两个参数还是非空List,匹配变长特化:校验
double和double兼容通过,剩余参数为List<>和List<>,进入递归。 - 传入的两个参数都是空List,匹配空列表全特化版本,返回
true。所有递归层级的与逻辑运算全部为真,最终结果为true,static_assert校验通过。
这个特化不会和其他特化产生冲突:当第二个参数是空List时,根本不满足“第二个参数是至少包含1个元素的List”的模式要求,编译器不会选中该版本,会优先匹配约束更严格的“第二个参数为空List”的偏特化版本。
内容的提问来源于stack exchange,提问作者stackr

