如何限制C++14模板化函数的实参范围?
C++14下限制泛型函数仅匹配特定模板实例的解决方案
问题背景
现有三组模板类型:
a::A<T>和b::B<T>:采用duck typing的相关类型foo::Foo<T>:功能类似但接口独立的类型
对应的代码定义如下:
#include <string> #include <iostream> namespace a { template<typename T> struct A final {}; using int_t = A<int>; using float_t = A<float>; } namespace b { template<typename T> struct B final {}; using int_t = B<int>; using float_t = B<float>; } namespace foo { template<typename T> struct Foo final {}; using int_t = Foo<int>; using float_t = Foo<float>; std::string f(const int_t&) { return "f(Foo<int>)\n"; } std::string g(const int_t&) { return "g(Foo<int>)\n"; } std::string h(const float_t&) { return "h(Foo<float>)\n"; } }
初始泛型函数的问题
最初编写的泛型f()匹配范围过广,会意外匹配基础类型:
template<typename T> std::string f(const T&) { return "f(T)\n"; } int main() { a::int_t a_int; b::int_t b_int; foo::int_t foo_int; std::cout << f(a_int); // 输出"f(T)" std::cout << f(b_int); // 输出"f(T)" std::cout << f(foo_int); // 输出"f(Foo<int>)"(匹配foo的重载) std::cout << f(314); // 意外匹配!输出"f(T)" std::cout << "\n"; }
用Trait缩小匹配范围(实现g())
通过自定义TraitAB,实现了仅接受a::A<T>和b::B<T>的泛型函数g():
template<typename Tab> struct AB final { AB() = delete; }; template<typename T> struct AB<a::A<T>> final { AB() = delete; using type = a::A<T>; }; template<typename T> struct AB<b::B<T>> final { AB() = delete; using type = b::B<T>; }; // 仅匹配AB范畴内的类型 template<typename Tab, typename type_ = typename AB<Tab>::type> std::string g(const Tab&) { return "g(Tab)\n"; }
测试结果符合预期:
int main() { a::int_t a_int; b::int_t b_int; foo::int_t foo_int; std::cout << g(a_int); // 输出"g(Tab)" std::cout << g(b_int); // 输出"g(Tab)" std::cout << g(foo_int); // 输出"g(Foo<int>)"(匹配foo的重载) // std::cout << g(314); // 编译失败,符合预期 std::cout << "\n"; }
待解决的问题:仅匹配float实例的h()
需要实现仅接受a::A<float>或b::B<float>的h(),但当前写法会错误匹配int版本:
// 错误写法:匹配所有AB范畴类型,包括int实例 template<typename Tab, typename type_ = typename AB<Tab>::type> std::string h(const Tab&) { return "h(Tab<float>)\n"; } // 测试结果 int main() { a::float_t a_float; b::float_t b_float; foo::float_t foo_float; a::int_t a_int; b::int_t b_int; std::cout << h(a_float); // 正确输出"h(Tab<float>)" std::cout << h(b_float); // 正确输出"h(Tab<float>)" std::cout << h(foo_float); // 输出"h(Foo<float>)"(匹配foo的重载) std::cout << h(a_int); // 错误匹配!输出"h(Tab<float>)" std::cout << h(b_int); // 错误匹配!输出"h(Tab<float>)" // std::cout << h(foo_int); // 编译失败,符合预期 // std::cout << h(314); // 编译失败,符合预期 }
C++14可行解决方案
步骤1:优化AB Trait,暴露内部模板参数
修改AB Trait,新增value_type成员,用于获取a::A<T>/b::B<T>的模板参数T:
#include <type_traits> // 引入std::is_same、std::enable_if_t等 template<typename Tab> struct AB final { AB() = delete; // 默认版本无value_type,触发SFINAE }; template<typename T> struct AB<a::A<T>> final { AB() = delete; using type = a::A<T>; using value_type = T; // 暴露内部模板参数 }; template<typename T> struct AB<b::B<T>> final { AB() = delete; using type = b::B<T>; using value_type = T; // 暴露内部模板参数 };
步骤2:实现带类型检查的h()函数
利用C++14的std::enable_if_t结合value_type,限制仅匹配T=float的实例:
// 方案1:直接在模板参数中检查 template<typename Tab, typename = typename AB<Tab>::type, // 确保属于AB范畴 typename T = typename AB<Tab>::value_type, typename = std::enable_if_t<std::is_same<T, float>::value>> std::string h(const Tab&) { return "h(Tab<float>)\n"; }
或者用更清晰的辅助Trait封装检查逻辑:
// 辅助Trait:判断类型是否属于AB范畴且value_type为float template<typename T, typename = void> struct is_ab_float : std::false_type {}; template<typename Tab> struct is_ab_float<Tab, std::void_t<typename AB<Tab>::type, typename AB<Tab>::value_type>> : std::is_same<typename AB<Tab>::value_type, float> {}; // 基于辅助Trait的h()函数 template<typename Tab, typename = std::enable_if_t<is_ab_float<Tab>::value>> std::string h(const Tab&) { return "h(Tab<float>)\n"; }
测试验证
修改后的h()函数仅接受a::A<float>和b::B<float>,其他类型会编译失败:
int main() { a::float_t a_float; b::float_t b_float; foo::float_t foo_float; a::int_t a_int; b::int_t b_int; std::cout << h(a_float); // 正常编译,输出"h(Tab<float>)" std::cout << h(b_float); // 正常编译,输出"h(Tab<float>)" // std::cout << h(foo_float); // 编译失败,foo::Foo不属于AB范畴 // std::cout << h(a_int); // 编译失败,value_type为int // std::cout << h(b_int); // 编译失败,value_type为int // std::cout << h(314); // 编译失败,不属于AB范畴 }
备选方案:直接重载(适用于类型数量少的场景)
如果仅需支持a::float_t和b::float_t,也可以直接写重载函数,无需泛型:
std::string h(const a::float_t&) { return "h(Tab<float>)\n"; } std::string h(const b::float_t&) { return "h(Tab<float>)\n"; }
这种写法更直观,但扩展性差,若新增类似类型(如c::C<float>)需要手动添加重载。
内容的提问来源于stack exchange,提问作者Ван
相关产品推荐
相关产品推荐

