为什么我应该避免函数签名中的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,您需要非常小心才能正确使用。
- 如果
enable_if
中的条件包含嵌套的模板或types定义(提示:look for::
tokens),则这些嵌套的tempatles或types的parsing通常是非推导的上下文 。 在这样一个非推断的上下文中的任何replace失败都是一个错误 。 - 多个
enable_if
重载中的各种条件不能有任何重叠,因为重载parsing是不明确的。 这是你作为一个作者需要检查自己,虽然你会得到很好的编译器警告。 -
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
,但对于大多数常规编译时使用条件,不推荐使用。