模板基础与 SFINAE1. 函数模板与类模板的区别面试题函数模板和类模板在使用上有什么关键区别函数模板支持模板参数推导——调用时不需要指定模板参数编译器根据实参类型自动推导#includeiostream#includetype_traitstemplatetypenameTTmax(T a,T b){returnab?a:b;}// 调用max(1, 3) → T 推导为 int// 调用max(3.14, 2.71) → T 推导为 double类模板在 C17 之前必须显式指定模板参数templatetypenameTstructBox{T value;explicitBox(T v):value(v){}};Boxintb(42);// 必须写 int// Box b2(42); // C17 前错误C17 CTAD类模板参数推导允许从构造函数推导Boxb2(42);// C17 起合法推导为 BoxintBoxb3(3.14);// BoxdoubleCTAD 依赖推导指引deduction guide用户也可自定义templatetypenameTstructVec{T x,y;Vec(T a,T b):x(a),y(b){}};// 隐式推导指引已足够Vecv(1,2);// Vecint特性函数模板类模板参数推导自动推导C17 前必须显式指定CTAD 后可推导偏特化不支持支持默认参数支持支持2. 模板特化与偏特化面试题模板特化和偏特化分别解决什么问题全特化为某个具体类型提供完整实现用template开头#includeiostream#includecstringtemplatetypenameTstructComparator{staticboolequals(constTa,constTb){returnab;}};// 全特化const char* 用 strcmp 比较templatestructComparatorconstchar*{staticboolequals(constchar*a,constchar*b){returnstd::strcmp(a,b)0;}};偏特化仅部分约束模板参数。只有类模板支持偏特化函数模板不支持#includeiostream// 主模板templatetypenameT,typenameUstructPair{};// 偏特化两个类型相同时templatetypenameTstructPairT,T{};// 偏特化第二个参数为 int 时templatetypenameTstructPairT,int{};// 偏特化指针类型templatetypenameTstructPairT*,T*{};// 偏特化模板模板参数templatetemplatetypenameclassContainer,typenameTstructWrapper{};templatetypenameTstructWrapperstd::vector,T{};函数模板无法偏特化但可以用重载达到类似效果templatetypenameTvoidprocess(T v);// 主模板templatetypenameTvoidprocess(T*v);// 重载指针版本而非偏特化templatevoidprocessint(intv);// 全特化函数模板可以要点函数模板用重载代替偏特化。如果你需要写template typename T void fooT*()改为写template typename T void foo(T*)即可。3. SFINAE 原则与 enable_if面试题什么是 SFINAE用 enable_if 实现过什么SFINAESubstitution Failure Is Not An Error是 C 模板实例化时的核心原则当模板参数替换产生非法类型或表达式时不报错只是将该候选从重载集中移除。#includeiostream#includetype_traits// 当 T 是整数类型时启用templatetypenameTtypenamestd::enable_ifstd::is_integralT::value,void::typecheck(T v){std::cout整数: v\n;}// 当 T 是浮点类型时启用templatetypenameTtypenamestd::enable_ifstd::is_floating_pointT::value,void::typecheck(T v){std::cout浮点数: v\n;}intmain(){check(42);// 整数check(3.14);// 浮点数// check(hello); // 编译错误没有匹配的重载}等价写法C14 起可用std::enable_if_ttemplatetypenameTstd::enable_if_tstd::is_integralT::value,voidcheck(T v);void_t 检测惯用法C17#includeiostream#includetype_traits#includeutilitytemplatetypename,typenamevoidstructhas_member_foo:std::false_type{};// 偏特化如果 T::foo 存在且类型合法匹配此版本templatetypenameTstructhas_member_fooT,std::void_tdecltype(std::declvalT().foo()):std::true_type{};structA{voidfoo(){}};structB{};static_assert(has_member_fooA::value,A 有 foo());static_assert(!has_member_fooB::value,B 没有 foo());void_t将任何类型映射为void。如果decltype(...)非法即成员不存在替换失败退回到std::false_type。是SFINAE否唯一候选多个候选且二义无候选编译器看到重载集对每个候选做参数替换替换产生非法类型或非法表达式从重载集剔除该候选保留为候选还有候选吗编译通过 ✓编译错误二义调用编译错误无匹配函数4. 变参模板与折叠表达式C17面试题变参模板怎么展开折叠表达式有几种形式递归展开C11 经典方式#includeiostream// 递归终止函数voidprint_all(){}templatetypenameT,typename...Argsvoidprint_all(T first,Args...rest){std::coutfirst ;print_all(rest...);// 递归展开}intmain(){print_all(1,3.14,hello,!);// 输出: 1 3.14 hello !}折叠表达式C17更简洁零递归开销#includeiostreamtemplatetypename...Argsautosum(Args...args){return(...args);// 一元左折叠((a b) c) d}templatetypename...Argsvoidprint_all(Args...args){((std::coutargs ),...);// 逗号折叠}templatetypename...Argsboolall_true(Args...args){return(...args);// 逻辑与折叠}intmain(){std::coutsum(1,2,3,4)\n;// 10print_all(1,3.14,hello);// 1 3.14 hellostd::coutstd::boolalphaall_true(true,true,false);// false}三种折叠形式形式语法展开结果以 为例一元左折叠(... args)((a b) c) d一元右折叠(args ...)a (b (c d))二元左折叠(init ... args)(((init a) b) c) d二元右折叠(args ... init)a (b (c (d init)))整数加法左折叠和右折叠结果相同加法可结合但减法不同——优先用左折叠更符合直觉。5. Concepts 如何替代 SFINAEC20面试题C20 的 concepts 相比 enable_if 好在哪里Concepts 让模板约束变成可命名的类型谓词可读性远超 SFINAE 写法且错误信息会直接指出违反的 concept 名称而非一长串模板实例化栈。基本用法#includeiostream#includeconcepts// 定义 concepttemplatetypenameTconceptIntegralstd::is_integral_vT;templatetypenameTconceptFloatingPointstd::is_floating_point_vT;// 方式一requires 子句templatetypenameTrequiresIntegralTThalf(T v){returnv/2;}// 方式二受约束模板参数templateFloatingPoint TThalf(T v){returnv/2.0;}// 方式三auto 缩写更简洁voidprint(Integralautov){std::cout整数: v\n;}复杂约束与 requires 表达式#includeiostream#includeconcepts#includevector// 检查类型是否可调用函数式编程风格templatetypenameF,typenameTconceptCallableWithrequires(F f,T v){{f(v)}-std::same_asT;// 返回类型必须为 T};// 检查容器是否可迭代templatetypenameCconceptIterablerequires(C c){{std::begin(c)}-std::input_or_output_iterator;{std::end(c)}-std::input_or_output_iterator;};templateIterable Cvoidforeach(constCcontainer){for(constautov:container)std::coutv ;}SFINAE vs Concepts 对比#includeiostream#includetype_traits#includeconcepts// SFINAE 写法C11/17templatetypenameTstd::enable_if_tstd::is_arithmetic_vT,Tsfinae_func(T v){returnv*2;}// Concepts 写法C20templatetypenameTrequiresstd::is_arithmetic_vTTconcept_func(T v){returnv*2;}// 错误信息对比// sfinae_func(bad); // 几十行模板实例化栈// concept_func(bad); // 直接报未满足 is_arithmetic_vConcepts 支持更精确的重载#includeiostream#includeconceptstemplatetypenameTrequiresstd::integralTvoiddispatch(T){std::cout整数\n;}templatetypenameTrequiresstd::floating_pointTvoiddispatch(T){std::cout浮点数\n;}// concepts 的偏序规则比 SFINAE 更清晰// 带 concept 的模板比不带约束的模板更特化总结对照表特性函数模板类模板全特化支持支持偏特化不支持用重载替代支持CTADC17不适用支持SFINAE 使用常用返回/参数类型常用偏特化 void_t约束机制优缺点enable_ifC11灵活但可读性差错误信息难懂void_t惯用法C17适合做类型检测 trait可读仍一般ConceptsC20可命名、可复用、错误信息友好优先选用