如何为模板函数添加条件约束,使其仅适配继承自模板化BaseClass的派生类
嗨,我来帮你搞定这个问题!你现在需要写一个模板函数,只让那些继承自BaseClass<N>(不管N是哪个具体的size_t数值)的派生类能被这个函数接受对吧?刚好我有几种实用的实现方式,给你一步步说清楚。
首先先把你的原代码贴出来,方便我们对照:
#include <cstddef> template<size_t N> class BaseClass { size_t value = N; }; class Derived1 : public BaseClass<1> {}; class Derived2 : public BaseClass<2> {};
方案一:C++20 概念(Concepts)—— 最简洁直观的方式
C++20引入的Concepts专门用来解决模板参数的约束问题,代码可读性极强,是首选方案。
我们可以先定义一个概念(Concept),用来检测某个类型T是否是BaseClass<N>的派生类(包括BaseClass本身):
#include <concepts> // 定义概念:检查T是否能隐式转换为某个BaseClass<N> template<typename T> concept IsBaseClassDerived = requires { // 尝试将T的实例绑定到BaseClass<N>的引用,能成功就说明是派生类 []<size_t N>(const BaseClass<N>&) {}(std::declval<const T&>()); };
或者还有更直接的写法,利用BaseClass里的value成员来推导N:
template<typename T> concept IsBaseClassDerived = requires(T t) { // 尝试将T转换为BaseClass<N>,N由t.value的类型推导(这里t.value是size_t,对应BaseClass的模板参数) static_cast<const BaseClass<decltype(t.value)>&>(t); };
有了这个概念之后,我们的模板函数就可以直接用它来约束参数了:
#include <iostream> // 只有满足IsBaseClassDerived约束的类型T才能调用这个函数 template<IsBaseClassDerived T> void process(const T& obj) { // 这里可以安全访问BaseClass的成员,比如value std::cout << "处理继承自BaseClass的对象,value值为:" << obj.value << std::endl; }
现在测试一下:
int main() { Derived1 d1; Derived2 d2; process(d1); // 正常编译,输出:处理继承自BaseClass的对象,value值为:1 process(d2); // 正常编译,输出:处理继承自BaseClass的对象,value值为:2 // 下面这行代码会编译报错,因为int不满足约束 // process(123); return 0; }
方案二:C++11/14 SFINAE 方式—— 兼容旧标准
如果你需要兼容C11或C14,就没法用Concepts了,这时候可以用SFINAE(替换失败不是错误)的技巧来实现约束。
首先我们需要写一个类型特性(type trait)来检测类型是否符合要求:
#include <type_traits> namespace detail { // 重载1:接受BaseClass<N>的引用,返回std::true_type template<size_t N> std::true_type test(const BaseClass<N>&); // 重载2:匹配所有其他类型,返回std::false_type std::false_type test(...); } // 类型特性:判断T是否是BaseClass<N>的派生类 template<typename T> struct is_base_class_derived : decltype(detail::test(std::declval<const T&>())) {}; // 方便使用的变量模板(C++14及以上支持,C++11可以用static const bool代替) template<typename T> constexpr bool is_base_class_derived_v = is_base_class_derived<T>::value;
然后用std::enable_if来约束模板函数:
// C++11/14兼容的模板函数 template<typename T, typename = typename std::enable_if<is_base_class_derived_v<T>>::type> void process(const T& obj) { std::cout << "处理继承自BaseClass的对象,value值为:" << obj.value << std::endl; }
或者你也可以把enable_if放在返回值里,效果是一样的:
template<typename T> typename std::enable_if<is_base_class_derived_v<T>>::type process(const T& obj) { std::cout << "处理继承自BaseClass的对象,value值为:" << obj.value << std::endl; }
这个方案的原理是:当T是BaseClass<N>的派生类时,detail::test(std::declval<const T&>())会匹配到第一个重载,返回std::true_type,is_base_class_derived<T>就继承自std::true_type;否则匹配第二个重载,返回std::false_type,enable_if就会触发SFINAE,让这个模板函数被排除在候选列表之外,编译器就会报错说找不到合适的重载。
备注:内容来源于stack exchange,提问作者Maxi mini

