如何让库模板用户仅在特定场景下显式标记允许的模板参数?
场景背景
我开发的库中有一组类,可被应用程序以单个成员或数组形式“安装”到其他类中,数组的索引类型由应用程序决定(无需数组时使用void),可安装类本身无法控制索引类型。
为避免输入错误导致不必要的特化编译通过,我希望让客户端显式确认允许的关联,以此约束索引类型为应用程序预期的类型。尝试用ODR和类成员显式特化实现,库代码如下:
库代码实现
namespace foo { template <class P, class ROLE> struct association { static_assert(std::is_enum_v<ROLE>||std::is_integral_v<ROLE>); static const bool allowed(); }; template <class T> class bar final { public: bar() = default; ~bar() = default; }; void do_something() {} template <class I, class ROLE> void install(I &&i, ROLE r) { if (association<std::decay_t<I>, ROLE>::allowed()) do_something(); } template <class I> void install(I &&i) { if (association<std::decay_t<I>, void>::allowed()) do_something(); } }
客户端示例代码
// declare the indexing type enum myindex { min=0, max=3 }; int main() { foo::bar<int> foobar; foo::install(foobar, myindex::min); return 0; }
当前困境
如果不添加如下显式特化定义,上述代码会触发链接错误:
// add a definiton to make the association with myindex OK template <> const bool foo::association<bar<int>, myindex>::allowed() { return true; }
但实际场景中,部分模板(比如bar)本应支持任意索引类型,让用户为每一种组合编写特化过于繁琐。尝试写部分特化时会触发编译错误:
template <class T, class ROLE> const bool foo::association<foo::bar<T>, ROLE>::allowed () { return true; }
需求:是否可以针对bar的所有特化与任意ROLE组合,完整定义association::allowed(),同时强制用户为其他模板显式定义该函数?如果不可行,有没有更优方案(最好能结合static_assert,当前方式太繁琐)?
注:使用C++17,myindex无法纳入库中。
解决方案
1. 正确实现bar的部分特化
你之前的部分特化写法错误,因为C++不允许单独特化类模板的成员函数,必须先特化整个association类模板。正确的做法是:
namespace foo { // 为bar<T>与任意ROLE的组合做类模板部分特化 template <class T, class ROLE> struct association<bar<T>, ROLE> { static_assert(std::is_enum_v<ROLE>||std::is_integral_v<ROLE>); static const bool allowed() { return true; } }; // 单独处理bar<T>与void的关联(可选,若需要单独逻辑) template <class T> struct association<bar<T>, void> { static const bool allowed() { return true; } }; }
这样所有bar<T>与任意合法ROLE的组合都会自动拥有allowed()的定义,无需用户手动编写显式特化。对于其他类型,保持原association模板的allowed()只有声明无定义,用户未显式特化时会触发链接错误。
2. 编译期检测的更优方案(结合static_assert)
如果希望在编译期而非链接期检测非法关联,可以改用类型特征的方式,避免链接错误的不友好提示:
namespace foo { // 默认所有关联不允许 template <class P, class ROLE> struct is_association_allowed : std::false_type {}; // 允许bar<T>与任意合法ROLE关联 template <class T, class ROLE> struct is_association_allowed<bar<T>, ROLE> : std::true_type { static_assert(std::is_enum_v<ROLE>||std::is_integral_v<ROLE>, "ROLE must be enum or integral type"); }; // 允许bar<T>与void关联 template <class T> struct is_association_allowed<bar<T>, void> : std::true_type {}; void do_something() {} template <class I, class ROLE> void install(I &&i, ROLE r) { using P = std::decay_t<I>; static_assert(is_association_allowed<P, ROLE>::value, "This association is not allowed!"); do_something(); } template <class I> void install(I &&i) { using P = std::decay_t<I>; static_assert(is_association_allowed<P, void>::value, "This association is not allowed!"); do_something(); } }
客户端只需通过显式特化is_association_allowed来标记允许的关联:
// 示例:允许自定义类MyClass与myindex关联 template <> struct foo::is_association_allowed<MyClass, myindex> : std::true_type {};
这种方式的优势:
- 编译期直接给出错误提示,定位问题更高效
- 无需编写函数定义,仅需特化类型特征,代码更简洁
- 对
bar这类默认开放所有关联的模板,通过部分特化批量处理,无需逐个编写特化
原写法失败的原因
C++标准规定,不能单独特化类模板的成员函数,必须先对整个类模板进行特化(部分特化或全特化),再在特化的类中定义成员函数。你之前直接尝试特化association<foo::bar<T>, ROLE>::allowed(),但没有先特化association类模板本身,因此编译器报错。
内容的提问来源于stack exchange,提问作者Spencer

