为什么C++可推导函数指针单模板参数却无法推导为参数包?
问题描述
如下代码可在gcc与clang编译器下正常编译:
#define PACK //... template <typename Result, typename PACK Args, typename Last> auto g(Result (*f)(Args PACK, Last)) -> Result (*)(Args PACK) { return reinterpret_cast<Result (*)(Args PACK)>(f); } double af(char c, int i); auto ag{g(&af)};
但如果将第一行宏定义修改为:
#define PACK ...
两款编译器均无法通过编译,报错提示模板类型参数推导失败。为什么单独类型可以被成功推导,却无法作为(退化的)参数包被推导?
背景说明:笔者正在开发的应用利用了如下特性:使用
reinterpret_cast时,主流ABI均保证,若函数的参数类型是目标函数指针参数类型的前缀、且二者返回类型匹配,则将函数地址赋值给该函数指针是安全的。笔者最初尝试编写模板来静态校验该条件。
原因解析
两种场景编译结果不同的核心原因是C++模板的类型推导规则对固定参数和可变参数包的匹配要求不一样:
- 当
PACK定义为空时,宏展开后模板实际是固定三个类型参数的普通模板:template <typename Result, typename Args, typename Last>,函数入参的函数指针类型是Result (*)(Args, Last),属于完全固定的参数列表。传入&af(类型为double (*)(char, int))时,编译器可以直接按位置匹配:Result推导为double,Args推导为char,Last推导为int,完全符合常规模板推导逻辑,因此可以正常编译。 - 当
PACK定义为...时,宏展开后Args变成可变参数包,模板实际为template <typename Result, typename... Args, typename Last>,入参的函数指针类型是Result (*)(Args..., Last)。这个形式触发了C++标准规定的非推导上下文规则:
可变参数包如果没有出现在函数参数列表的最末尾,后面跟着其他独立的类型参数,编译器没有办法确定参数包的边界——也就是无法判断传入的函数参数列表中,哪一部分属于Args...,哪一部分属于最后面的Last。哪怕传入的函数参数长度固定、逻辑上看起来可以拆分,标准也不要求编译器做这种歧义推导,会直接判定为类型推导失败。
如果要实现参数前缀匹配的静态校验,不能把参数包放在非末尾位置,需要通过模板偏特化、或者C++17及以上版本的编译期判断逻辑,先拆分目标函数的参数列表,始终让可变参数包处于参数列表的最后位置,才能完成正常的类型推导。
内容的提问来源于stack exchange,提问作者WaltK
相关产品推荐
相关产品推荐

