C++17中如何基于前序模板参数为模板参数设置默认值?相关实现方案咨询
基于模板参数T1为T2设置默认值,无需手动指定的可行方案(C++17)
问题背景
我有一段模板函数代码:
using MyUInt = unsigned int; using MyLInt = long int; template<class T1, class T2> T1 fn(const T2& arg){ return T1(arg); } int main() { std::cout << fn<int, MyUInt>(1); std::cout << fn<double, MyLInt>(1); return 0; }
我希望能基于模板参数T1自动推导T2的默认值,这样调用时就不用手动声明T2了,比如实现类似下面的效果(虽然这个写法本身不合法,但代表我的需求):
using MyUInt = unsigned int; using MyLInt = long int; template<class T1, class T2 = typename std::is_same<T1, int>::value ? MyUInt : MyLInt> T1 fn(const T2& arg){ return T1(arg); } int main() { std::cout << fn<int>(1); std::cout << fn<double>(1); return 0; }
现在使用C++17标准,想知道有没有可行的实现方式?
可行解决方案
当然可以实现!你最初尝试的写法出错是因为三元运算符是运行时表达式,而模板参数的默认值必须是编译期可确定的类型。下面是几种适配C++17的优雅方案:
方案1:用std::conditional_t实现编译期类型选择
C11引入的std::conditional(C14起有简化别名std::conditional_t)是专门用于编译期类型分支的工具,搭配C++17的std::is_same_v变量模板可以简化代码:
#include <type_traits> #include <iostream> using MyUInt = unsigned int; using MyLInt = long int; template<class T1, class T2 = std::conditional_t<std::is_same_v<T1, int>, MyUInt, MyLInt>> T1 fn(const T2& arg){ return T1(arg); } int main() { // 只需指定T1,T2会根据T1自动匹配默认值 std::cout << fn<int>(1) << '\n'; // T2默认是MyUInt std::cout << fn<double>(1) << '\n'; // T2默认是MyLInt return 0; }
这里std::is_same_v<T1, int>等价于std::is_same<T1, int>::value,是C++17新增的语法糖,让代码更简洁。
方案2:自定义类型Trait(扩展性更强)
如果后续需要给更多T1类型设置对应的T2默认值,自定义类型Trait会让代码更易维护:
#include <type_traits> #include <iostream> using MyUInt = unsigned int; using MyLInt = long int; // 定义一个Trait模板,用于映射T1到默认T2 template<typename T> struct DefaultT2; // 特化:当T1是int时,默认T2为MyUInt template<> struct DefaultT2<int> { using type = MyUInt; }; // 特化:当T1是double时,默认T2为MyLInt template<> struct DefaultT2<double> { using type = MyLInt; }; // 为未特化的其他类型设置通用默认值 template<typename T> struct DefaultT2 { using type = long int; }; // 定义别名简化Trait的使用 template<typename T> using DefaultT2_t = typename DefaultT2<T>::type; // 模板函数使用自定义Trait作为T2的默认值 template<class T1, class T2 = DefaultT2_t<T1>> T1 fn(const T2& arg){ return T1(arg); } int main() { std::cout << fn<int>(1) << '\n'; // T2是MyUInt std::cout << fn<double>(1) << '\n'; // T2是MyLInt std::cout << fn<float>(100) << '\n'; // T2是通用默认的long int return 0; }
后续新增T1和T2的对应关系时,只需要添加新的DefaultT2特化即可,逻辑清晰且扩展性强。
方案3:函数重载(补充方案)
如果你的场景允许依赖参数类型推导,也可以通过重载函数来实现类似效果,但这种方式依赖传入参数的类型,灵活性不如前两种:
#include <iostream> using MyUInt = unsigned int; using MyLInt = long int; // 基础模板函数 template<class T1, class T2> T1 fn(const T2& arg){ return T1(arg); } // 重载:针对int返回值,默认用MyUInt作为参数类型 int fn(const MyUInt& arg) { return fn<int, MyUInt>(arg); } // 重载:针对double返回值,默认用MyLInt作为参数类型 double fn(const MyLInt& arg) { return fn<double, MyLInt>(arg); } int main() { std::cout << fn(1U) << '\n'; // 自动匹配int版本,T1=int,T2=MyUInt std::cout << fn(1L) << '\n'; // 自动匹配double版本,T1=double,T2=MyLInt return 0; }
内容的提问来源于stack exchange,提问作者hmmmmmm
相关产品推荐
相关产品推荐

