如何将C++模板模板参数约束为模板化类型的子类?
如何将C++模板模板参数约束为模板化类型的子类?
嘿,这个问题问得特别到位!你现在的代码其实已经能正常跑起来,但有个小问题——它没有编译期的明确约束。要是有人不小心传了一个和base_type_t八竿子打不着的单参数模板,编译器只会在UseModel里实例化对象的时候才报错,而且错误信息可能绕得你头大。
咱们可以用C++的特性给模板模板参数加上清晰的约束,既能明确代码意图,又能在编译期就把不符合要求的用法拦下来,错误提示还友好。下面分两种情况给你讲:
第一种:用C++20的Concepts(最优雅的方式)
C++20引入的概念(Concepts)就是专门用来做这种编译期约束的,语法直观,错误提示也清晰。不过要先做个小修改:把base_type_t的k1模板参数暴露成公共的静态常量,这样咱们才能在外面验证它。
- 先修改基类,暴露
k1:
template<typename T1, int k1> struct base_type_t { static constexpr int k1_value = k1; // 把k1变成公共可访问的常量 };
- 定义一个Concept,检查模板是否符合要求:
咱们要检查的是:这个单参数模板实例化后,必须是base_type_t的派生类,而且它固定的k1值得和基类的对应上。
#include <concepts> // 别忘了包含这个头文件 template<template<typename> typename T> concept BaseTypeDerivedTemplate = requires { // 首先得确保T能被任意类型(比如void)实例化 typename T<void>; // 然后验证T<void>确实是base_type_t的派生类,且k1值匹配 requires std::derived_from<T<void>, base_type_t<void, T<void>::k1_value>>; };
- 给
UseModel加上约束:
直接用刚才定义的Concept修饰模板模板参数就行:
template<BaseTypeDerivedTemplate TemplatedType> void UseModel() { TemplatedType<float> instance1 {}; TemplatedType<bool> instance2 {}; }
这样一来,要是有人传个不符合的模板,比如:
template<typename T> struct WrongTemplate {}; // 和base_type_t完全无关的模板 UseModel<WrongTemplate>(); // 这行会直接编译报错,提示不符合Concept约束
编译器会直接告诉你这个模板不满足BaseTypeDerivedTemplate的要求,而不是在实例化的时候才抛一堆看不懂的错误。
第二种:用C++17及更早的SFINAE
如果你还在使用C++17或者更早的版本,没法用Concepts,那可以用SFINAE(替换失败不是错误)来实现同样的效果。步骤类似,还是先暴露基类的k1:
- 同样先修改基类:
template<typename T1, int k1> struct base_type_t { static constexpr int k1_value = k1; };
- 定义一个类型特征(Type Trait)来检查模板是否符合要求:
类型特征是C++元编程里常用的技巧,用来在编译期判断类型是否满足某些条件。
#include <type_traits> // 默认情况:不符合要求,值为false template<template<typename> typename T, typename = void> struct IsBaseTypeDerived : std::false_type {}; // 特化版本:当模板T符合要求时,值为true template<template<typename> typename T> struct IsBaseTypeDerived<T, std::void_t< // 检查T<void>有k1_value这个成员 decltype(T<void>::k1_value), // 检查T<void>确实是base_type_t的派生类 std::enable_if_t<std::is_base_of_v<base_type_t<void, T<void>::k1_value>, T<void>>> >> : std::true_type {}; // 定义一个变量模板,方便直接使用 template<template<typename> typename T> constexpr bool IsBaseTypeDerived_v = IsBaseTypeDerived<T>::value;
- 给
UseModel加上SFINAE约束:
通过std::enable_if来过滤不符合要求的模板:
template<template<typename> typename TemplatedType, // 只有当IsBaseTypeDerived_v为true时,这个重载才会被选中 typename = std::enable_if_t<IsBaseTypeDerived_v<TemplatedType>>> void UseModel() { TemplatedType<float> instance1 {}; TemplatedType<bool> instance2 {}; }
这样,要是传不符合的模板,编译器会因为找不到匹配的UseModel重载而报错,虽然错误提示不如Concepts直观,但也能明确问题所在。
完整的C++20版本示例代码
#include <concepts> template<typename T1, int k1> struct base_type_t { static constexpr int k1_value = k1; }; template<typename T1> struct foo_t : public base_type_t<T1, 1> {}; template<typename T1> struct bar_t : public base_type_t<T1, 1> {}; template<typename T1> struct baz_t : public base_type_t<T1, 16> {}; template<template<typename> typename T> concept BaseTypeDerivedTemplate = requires { typename T<void>; requires std::derived_from<T<void>, base_type_t<void, T<void>::k1_value>>; }; template<BaseTypeDerivedTemplate TemplatedType> void UseModel() { TemplatedType<float> instance1 {}; TemplatedType<bool> instance2 {}; } // 测试用的不符合模板(打开注释会报错) // template<typename T> // struct WrongTemplate {}; int main() { UseModel<foo_t>(); UseModel<bar_t>(); UseModel<baz_t>(); // UseModel<WrongTemplate>(); // 编译报错:不符合Concept约束 return 0; }
总结一下哈:通过把基类的模板参数暴露为公共成员,再结合C20的Concepts或者C17的SFINAE,就能完美实现你要的约束。这样不仅代码意图更清晰,还能在编译期就拦截错误使用,比等到实例化的时候报错要友好得多。
内容来源于stack exchange
相关产品推荐
相关产品推荐

