本章中遇到这些技术时已经推荐了一些最有用的 SFINAE 和基于概念的技术,但由于涵盖的内容很多,简明扼要地重申这些指导原则可能会有所帮助。这些指导原则主要是为在应用程序代码中使用模板的开发者准备的。这包括应用程序核心模板库等基础代码,但编写像 STL 这样为最广泛可能用途而设计、在极其多样的条件下使用,并以正式标准精确记录的库的开发者,会发现这些指导原则缺乏精确性和正式性:
掌握 SFINAE 的基本规则:了解它在哪些上下文中适用(声明),在哪些上下文中不适用(函数体)。
优先使用“自然”的 SFINAE:在模板声明中使用依赖类型,在尾部返回类型中使用依赖表达式,这总是表达模板参数约束的最简单方式(但请参见下一条准则)。
反思依赖类型的使用:问问自己,使用 T::value_type 这样的依赖类型,是因为它恰好是使用上下文中的正确类型,还是仅仅因为它比编写真正的接口约束(例如“可转换为 T::value_type 的类型”)更简单?在后一种情况下,本章应该已经能说服你,这样的约束并不难表达。
尽可能使模板更通用:通过使用其他的模板参数并对其施加必要的约束,而不是直接使用 T::value_type 作为参数类型,而是使用另一个模板参数并约束其可转换为 T::value_type。
优先使用 C++20 概念:如果使用 C++20 并能使用概念,应避免使用“人为的”SFINAE,即不要创建唯一目的是约束模板的替换失败。根据需要使用 requires 子句,无论是否使用概念。
选择统一的 SFINAE 方法:如果不能使用 C++20 概念,请选择一种基于 SFINAE 约束的通用统一方法并遵循它。即使无法使用语言工具,也要借鉴为 C++20 开发的概念式方法:在应用基于 SFINAE 的技术时,遵循相同的风格和模式。上一节就展示了一种这样的方法。
模板体应无替换错误,如果模板声明满足所有指定的限制,则模板体中不应出现替换错误(即,如果函数调用,就应该能编译)。这在实践中是一个困难的目标:限制可能会变得冗长且有时难以编写,甚至可能没有意识到实现隐含地要求了所有约束。即使是经过委员会仔细审查其需求中每个字的 STL,也未能完全实现这一目标。尽管如此,追求这一目标仍然是一个很好的实践。此外,如果必须允许函数被调用但不编译,至少应在函数体中使用 static_assert 将要求明确编码,相应的错误信息会更容易理解。