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

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

std::enable_if可以用作函数参数,作为返回types或作为类模板或函数模板参数来有条件地从重载parsing中移除函数或类。

在这个问题中,所有三个解决scheme都显示

作为函数参数:

 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; } }; 

作为返回types:

 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; } }; 
  • 应该select哪种解决scheme,为什么我应该避免其他解决scheme?
  • 在哪些情况下,“在函数签名中避免使用std :: enable_if”将使用作为返回types(它不是正常函数签名的一部分,而是模板特殊化的一部分)?
  • 会员和非会员function模板是否有区别?

把黑客入模板参数

enable_if模板参数方法至less有两个优点:

  • 可读性 :enable_if使用和返回/参数types不会合并成一个凌乱的types名称消歧器和嵌套types的访问块; 即使消歧器和嵌套types的混乱可以通过别名模板来缓解,但是仍然会将两个不相关的东西合并在一起。 enable_if使用与模板参数不相关的返回types。 把它们放在模板参数中意味着它们更接近于重要的东西;

  • 通用的适用性 :构造函数没有返回types,有些运算符不能有额外的参数,所以其他两个选项都不能适用。 将enable_if放在模板参数中无处不在,因为您只能在模板上使用SFINAE。

对我来说,可读性方面是这个select的重要因素。

std::enable_if模板参数推导期间依赖于“ replace失败不是错误 ”(aka SFINAE)原理。 这是一个非常脆弱的语言function,您需要非常小心才能正确使用。

  1. 如果enable_if中的条件包含嵌套的模板或types定义(提示:look for :: tokens),则这些嵌套的tempatles或types的parsing通常是非推导的上下文 。 在这样一个非推断的上下文中的任何replace失败都是一个错误
  2. 多个enable_if重载中的各种条件不能有任何重叠,因为重载parsing是不明确的。 这是你作为一个作者需要检查自己,虽然你会得到很好的编译器警告。
  3. enable_if在重载parsing过程中操作可行的函数集,根据从其他作用域(例如,通过ADL)引入的其他函数的存在,它们可能具有令人惊讶的交互作用。 这使得它不是很健壮。

总之,当它工作的时候是有效的,但是当它不能被debugging的时候会很困难。 一个非常好的select是使用标签分派 ,即委托给一个实现函数(通常在一个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 */) {} } 

标签分派不会操作重载集合,但可以通过编译时expression式(例如在types特征中)提供适当的参数来帮助您select正确的函数。 根据我的经验,这样debugging起来要容易得多。 如果您是复杂types特征的有抱负的库作家,您可能需要enable_if ,但对于大多数常规编译时使用条件,不推荐使用。