为什么我应该在函数签名中避免 std::enable_if

新手上路,请多包涵

Scott Meyers 发布了他的下一本书 EC++11 的 内容和状态。他写道,书中的一项内容可能是 “在函数签名中避免 std::enable_if

std::enable_if 可用作函数参数、返回类型或类模板或函数模板参数,以有条件地从重载决议中删除函数或类。

这个问题 中,显示了所有三个解决方案。

作为函数参数:

 template<typename T>
struct Check1
{
   template<typename U = T>
   U read(typename std::enable_if<
          std::is_same<U, int>::value >::type* = 0) { return 42; }

   template<typename U = T>
   U read(typename std::enable_if<
          std::is_same<U, double>::value >::type* = 0) { return 3.14; }
};

作为模板参数:

 template<typename T>
struct Check2
{
   template<typename U = T, typename std::enable_if<
            std::is_same<U, int>::value, int>::type = 0>
   U read() { return 42; }

   template<typename U = T, typename std::enable_if<
            std::is_same<U, double>::value, int>::type = 0>
   U read() { return 3.14; }
};

作为返回类型:

 template<typename T>
struct Check3
{
   template<typename U = T>
   typename std::enable_if<std::is_same<U, int>::value, U>::type read() {
      return 42;
   }

   template<typename U = T>
   typename std::enable_if<std::is_same<U, double>::value, U>::type read() {
      return 3.14;
   }
};

  • 应该首选哪种解决方案,为什么要避免使用其他解决方案?
  • 在哪些情况下, “在函数签名中避免 std::enable_if 涉及用作返回类型(这不是正常函数签名的一部分,而是模板特化的一部分)?
  • 成员函数模板和非成员函数模板有什么区别吗?

原文由 hansmaad 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 886
2 个回答

将 hack 放入模板参数 中。

模板参数方法上的 enable_if 至少有两个优点:

  • _可读性_: enable_if 使用和返回/参数类型没有合并到一个杂乱的类型名称消歧器和嵌套类型访问块中;即使可以使用别名模板来减轻消歧器和嵌套类型的混乱,但这仍然会将两个不相关的东西合并在一起。 enable_if 的使用与模板参数有关,与返回类型无关。将它们放在模板参数中意味着它们更接近重要的东西;

  • _普遍适用性_:构造函数没有返回类型,并且一些运算符不能有额外的参数,因此其他两个选项都不能应用于任何地方。将 enable_if 放在模板参数中适用于任何地方,因为无论如何您只能在模板上使用 SFINAE。

对我来说,可读性方面是这个选择的最大动力。

原文由 R. Martinho Fernandes 发布,翻译遵循 CC BY-SA 3.0 许可协议

std::enable_if模板参数推导 过程中依赖于“ 替换失败不是错误”(又名 SFINAE)原则。这是一个 非常脆弱 的语言功能,您需要非常小心才能使其正确。

  1. 如果您在 enable_if 中的条件包含嵌套模板或类型定义(提示:查找 :: 标记),那么这些嵌套模板或类型的解析通常 是非推断上下文.这种非推导上下文的任何替换失败都是 错误
  2. 多个 enable_if 重载中的各种条件不能有任何重叠,因为重载解析会模棱两可。这是作为作者的你需要自己检查的东西,尽管你会得到很好的编译器警告。
  3. enable_if 在重载解析期间操纵一组可行的函数,根据从其他范围(例如通过ADL)引入的其他函数的存在,这可能会产生令人惊讶的交互。这使得它不是很健壮。

简而言之,当它工作时它工作,但当它不工作时,它可能很难调试。一个很好的替代方法是使用 标签调度,即委托给一个实现函数(通常在 detail 命名空间或助手类中),该函数接收基于与您相同的编译时条件的虚拟参数在 enable_if 中使用。

 template<typename T>
T fun(T arg)
{
    return detail::fun(arg, typename some_template_trait<T>::type() );
}

namespace detail {
    template<typename T>
    fun(T arg, std::false_type /* dummy */) { }

    template<typename T>
    fun(T arg, std::true_type /* dummy */) {}
}

标记调度不操纵重载集,但通过编译时表达式(例如,在类型特征中)提供适当的参数,帮助您准确选择所需的函数。以我的经验,这更容易调试和正确。如果您是一个有抱负的复杂类型特征的库作者,您可能需要 enable_if 以某种方式,但对于大多数常规使用的编译时条件,不建议这样做。

原文由 TemplateRex 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题