std::type_identity处理可变参数列表的标准行为问询
关于std::type_identity在可变参数推导中行为的C++标准解读
问题背景
std::type_identity的核心作用是提供非推导上下文,避免模板参数被自动推导。但在测试其与可变参数列表结合的推导行为时,不同编译器出现了分歧:
- 第一个
bar调用可在GCC和MSVC中编译通过 - 第二个
bar调用仅能在MSVC中编译通过
需要明确C++标准下的正确行为。
示例代码
#include <type_traits> struct in_between{}; template <typename... T> struct args_tag { using type = std::common_type_t<T...>; }; template <typename... T> void bar(args_tag<T...>, std::type_identity_t<T>..., int, std::type_identity_t<T>...) {} template <typename... T> void bar(args_tag<T...>, std::type_identity_t<T>..., in_between, std::type_identity_t<T>...) {} // example int main() { bar(args_tag<int, int>{}, 4, 8, 15, 16, 23); bar(args_tag<int, int>{}, 4, 8, in_between{}, 16, 23); }
标准规则与行为分析
根据C++标准的模板参数推导规则:
- 模板参数的确定优先级:第一个函数参数
args_tag<T...>是模板特化类型,编译器可直接从实参args_tag<int, int>{}推导出模板参数包T...为int, int。 - 非推导上下文的处理:
std::type_identity_t<T>本质是typename std::type_identity<T>::type,属于标准定义的非推导上下文。这类上下文不会触发模板参数推导,编译器会直接使用已通过其他参数确定的T...结果。
针对两个调用的具体分析:
- 第一个
bar调用:
推导出T... = int, int后,函数参数列表展开为args_tag<int, int>, int, int, int, int, int,与调用传入的参数完全匹配,编译通过符合标准。 - 第二个
bar调用:
同理,推导出T... = int, int后,函数参数列表展开为args_tag<int, int>, int, int, in_between, int, int,与调用传入的参数完全匹配,同样应当编译通过。
结论
根据C++标准,两个bar调用都应该编译通过。MSVC的行为符合标准要求,GCC对第二个调用的编译失败属于编译器实现bug。
内容的提问来源于stack exchange,提问作者Sergey Kolesnik
相关产品推荐
相关产品推荐

