如何让模板参数推导时优先考虑某一个函数实参的类型?
嘿,这个问题我之前写位操作工具的时候也碰到过!刚好可以给你捋清楚怎么解决~
我先把你的场景再明确下:你写了这么一个模板函数来切换位标志:
template< typename Flags > void toggleFlag( Flags & targetFlags, Flags flagToSet, bool enabled ) { if (enabled) targetFlags |= flagToSet; else targetFlags &= ~flagToSet; }
但当你用枚举类型的标志去操作一个uint32_t变量时,比如这样:
enum MyFlags : uint32_t { Flag1 = (1 << 0), Flag2 = (1 << 1), }; uint32_t myFlags; toggleFlag(myFlags, Flag1, true); // 这里编译器会报错!
编译器会懵——因为第一个参数是uint32_t,第二个是MyFlags枚举类型,它不知道Flags模板参数该推导成哪一个,对吧?
核心问题就是:你想让模板优先根据第一个参数targetFlags的类型来推导Flags,让第二个参数自动适配这个类型,而不是让编译器因为类型歧义卡壳。下面给你两种可行的方案:
方案1:C++20及以上版本(最省心)
C++20标准库新增了std::type_identity,专门用来解决这种“不想让某个参数参与模板推导”的场景。你只需要把第二个参数的类型改成std::type_identity_t<Flags>就行:
#include <type_traits> template< typename Flags > void toggleFlag( Flags & targetFlags, std::type_identity_t<Flags> flagToSet, bool enabled ) { if (enabled) targetFlags |= flagToSet; else targetFlags &= ~flagToSet; }
这样一来,编译器只会从第一个参数targetFlags推导Flags的类型,第二个参数flagToSet会自动转换成这个推导出来的类型——比如上面的例子里,Flags会被推导成uint32_t,Flag1会自动转成uint32_t,完美解决歧义!
方案2:C++20之前的版本(手动实现小工具)
如果你的项目还在用C++17或更早的标准,没关系,我们可以自己实现一个简化版的type_identity,原理完全一样:
// 自己实现一个type_identity,用来屏蔽参数推导 template<typename T> struct type_identity { using type = T; }; template<typename T> using type_identity_t = typename type_identity<T>::type; template< typename Flags > void toggleFlag( Flags & targetFlags, type_identity_t<Flags> flagToSet, bool enabled ) { if (enabled) targetFlags |= flagToSet; else targetFlags &= ~flagToSet; }
这个自定义的type_identity_t和标准库的作用一模一样:它让第二个参数的类型依赖于已经推导出来的Flags,所以编译器不会尝试从第二个参数推导模板参数,只会跟着第一个参数走。
为啥这招管用?
简单说,模板参数推导的时候,对于std::type_identity_t<Flags>这种“依赖于模板参数的类型”,编译器会先从其他不依赖的参数(也就是第一个参数)推导出Flags,再用这个Flags来确定第二个参数的类型,然后做隐式类型转换。这样就彻底消除了推导歧义,完全符合你“优先用第一个参数类型”的需求~
备注:内容来源于stack exchange,提问作者Youda008

