我为什么要避免的std ::在函数签名enable_if(Why should I avoid st

2019-07-19 00:30发布

斯科特迈尔斯发布内容和状态他的下一本书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在函数签名”的关注用法返回类型(这是不正常的功能,但签名的模板特一部分)?
  • 是否有成员和非成员函数模板什么不同吗?

Answer 1:

把黑客的模板参数

enable_if模板参数方法比别人至少有两个好处:

  • 可读性 :所述enable_if使用和返回/参数类型不被合并在一起成为类型名称disambiguators和嵌套类型访问的一个凌乱的块; 即使消歧和嵌套类型的杂波可以用别名模板得到缓解,仍然会合并两个不相关的东西放在一起。 该enable_if的使用有关,而不是模板参数的返回类型。 让他们在模板参数意味着它们更接近于哪些事项;

  • 普遍适用性 :构造函数没有返回类型,以及一些运营商不能有额外的参数,所以没有其他两个选项都可以随处使用。 在模板参数把enable_if作品无处不在,因为你只能在模板中使用SFINAE反正。

对我来说,可读性方面是这一选择的大推动因素。



Answer 2:

std::enable_if依靠“ Substition失败不是一种错误模板实参推演过程(又称SFINAE)原则。 这是一个非常脆弱的语言特性,你需要非常小心地得到它的权利。

  1. 如果你的内部条件enable_if包含嵌套模板或类型定义(提示:寻找::代币),那么这些嵌套tempatles或类型的分辨率通常是一个非推断上下文 。 在这样一个非推测的上下文任何取代失败是错误的。
  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 */) {}
}

tag分派不操作重载集合,而是帮助你通过编译时表达提供适当的参数(例如,在型性状)选择正是你想要的功能。 根据我的经验,这是很容易调试,并得到正确的。 如果你是复杂的类型特征的抱负库编写者,你可能需要enable_if不知何故,但对于大多数经常使用的编译时间条件下,不推荐。



Answer 3:

其解决方案应该是首选,我为什么要避开别人?

  • 模板参数

    • 它是在构造函数中使用。
    • 它是在用户定义的转换运算符使用。
    • 它需要C ++ 11或更高版本。
    • 这是海事组织,更具可读性。
    • 这可能很容易被误用,并与重载产生错误:

       template<typename T, typename = std::enable_if_t<std::is_same<T, int>::value>> void f() {/*...*/} template<typename T, typename = std::enable_if_t<std::is_same<T, float>::value>> void f() {/*...*/} // Redefinition: both are just template<typename, typename> f() 

    注意typename = std::enable_if_t<cond>代替正确std::enable_if_t<cond, int>::type = 0

  • 返回类型:

    • 它不能在构造函数中使用。 (无返回类型)
    • 它不能在用户定义的转换运算符使用。 (未抵扣)
    • 可以使用预先C ++ 11。
    • 其次更具可读性海事组织。
  • 最后,在功能参数:

    • 可以使用预先C ++ 11。
    • 它是在构造函数中使用。
    • 它不能在用户定义的转换运算符使用。 (无参数)
    • 它不能与固定数量的参数的方法来使用(一元/二元运算+- * ,...)
    • 它可以安全地在继承使用(见下文)。
    • 更改函数签名(你已经基本上一个额外的最后一个参数void* = nullptr )(这样的函数指针也会有所不同,等等)

是否有成员和非成员函数模板什么不同吗?

有与继承和细微的差别using

按照using-declarator (重点煤矿):

namespace.udecl

该组由使用说明符引入声明的是通过在使用说明符的名称进行限定名查找([basic.lookup.qual],[class.member.lookup]),不包括被隐藏作为描述的功能的发现下面。

...

当使用说明符带来从基类声明成派生类,在派生类超控和/或隐藏成员函数和成员函数的模板具有相同的名称,参数类型列表,CV-成员函数和成员函数模板资格,和ref-限定符(如果有的话)在一个基类 (而不是相互矛盾的)。 这种隐藏或覆盖声明被排除在该组由使用说明符引入声明。

因此,对于这两个模板参数和返回值类型,都隐藏方法是以下情况:

struct Base
{
    template <std::size_t I, std::enable_if_t<I == 0>* = nullptr>
    void f() {}

    template <std::size_t I>
    std::enable_if_t<I == 0> g() {}
};

struct S : Base
{
    using Base::f; // Useless, f<0> is still hidden
    using Base::g; // Useless, g<0> is still hidden

    template <std::size_t I, std::enable_if_t<I == 1>* = nullptr>
    void f() {}

    template <std::size_t I>
    std::enable_if_t<I == 1> g() {}
};

演示 (GCC错误地认定的基函数)。

而用的说法,类似的情景作品:

struct Base
{
    template <std::size_t I>
    void h(std::enable_if_t<I == 0>* = nullptr) {}
};

struct S : Base
{
    using Base::h; // Base::h<0> is visible

    template <std::size_t I>
    void h(std::enable_if_t<I == 1>* = nullptr) {}
};

演示



文章来源: Why should I avoid std::enable_if in function signatures