首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >为什么我应该在函数签名中避免使用std::enable_if

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

Stack Overflow用户
提问于 2013-01-30 17:07:28
回答 4查看 38.7K关注 0票数 174

Scott Meyer发布了他的下一本书EC++11的content and status,他在书中写道,书中的一条可能是“在函数签名中避免std::enable_if”。

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

this question中,显示了所有三种解决方案。

作为函数参数:

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”涉及到作为返回类型的使用(这不是普通函数签名的一部分,而是模板specializations)?
  • Are的一部分。成员函数模板和非成员函数模板有什么不同?
EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2013-01-31 18:42:28

将hack放在模板参数中。

与其他方法相比,enable_if on template参数方法至少有两个优点:

  • 可读性: enable_if的使用和返回/参数类型没有合并到一个混乱的typename消歧器和嵌套类型访问块中;即使消歧器和嵌套类型的混乱可以通过别名模板减轻,这仍然会将两个不相关的东西合并在一起。enable_if的使用与模板参数相关,而与返回类型无关。将它们放在模板参数中意味着它们更接近matters;
  • universal的适用性:构造函数没有返回类型,一些运算符不能有额外的参数,所以其他两个选项都不能应用到所有地方。将enable_if放在模板参数中可以在任何地方使用,因为无论如何都只能在模板上使用SFINAE。

对我来说,可读性是这个选择的主要动机因素。

票数 114
EN

Stack Overflow用户

发布于 2013-01-30 17:33:52

std::enable_if模板参数演绎过程中依赖于"“(又名SFINAE)原则。这是一个非常脆弱的语言特性,您需要非常小心才能正确使用它。

  1. 如果enable_if中的条件包含嵌套模板或类型定义(提示:查找::标记),则这些嵌套模板或类型的解析通常是非推导上下文。这种非推导上下文上的任何替换失败都是error.
  2. the多个enable_if重载中的各种条件不能有任何重叠,因为重载解析将是不明确的。这是你作为作者需要检查自己的东西,尽管你会得到好的编译器warnings.
  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,但是对于大多数经常使用的编译时条件,不推荐使用它。

票数 58
EN

Stack Overflow用户

发布于 2018-03-28 18:56:03

哪种解决方案应该是首选的,为什么我应该避免其他解决方案?

选项1:模板参数中的enable_if

  • 它在构造函数中可用。

  • 它在用户定义的转换运算符中可用。

  • 它需要C++11或更高版本。

  • 在我看来,它更具可读性。

  • 很容易被误用并在重载时产生错误:

template::value>> void f() {/*...*/} template::value>> void f() {/*...*/} //重新定义:两者都只是template f()

请注意,使用了typename = std::enable_if_t<cond>而不是正确的std::enable_if_t<cond, int>::type = 0

选项2:返回类型中的enable_if

它不能与构造函数(没有返回类型)一起使用,也不能在用户定义的转换运算符中使用(因为它不是deducible)

  • It pre-C++11.

  • Second
  • 可读性更好的IMO。

选项3:函数参数中的enable_if

在用户定义转换运算符中不能使用函数签名(它们没有pre-C++11.

  • It,不能在具有固定数量的参数的方法中使用,如一元/二元运算符

  • +-*和其他。

  • 它在继承中使用是安全的(参见below).

  • Changes
  • (你基本上有一个额外的作为最后一个参数的void* = nullptr);这会导致指向函数的指针行为不同,等等。

成员函数模板和非成员函数模板有什么区别吗?

继承和using之间有细微的区别

根据using-declarator (强调我的观点):

namespace.udecl

使用声明程序引入的声明集是通过对使用声明程序中的名称执行限定名称查找(basic.lookup.qual,class.member.lookup)来找到的,但不包括如下所述的隐藏函数。..。当使用声明符将声明从基类带入派生类时,派生类中的成员函数和成员函数模板覆盖和/或隐藏基类中具有相同名称、参数类型列表、cv限定和引用限定符(如果有)的成员函数和成员函数模板(而不是冲突)。这样的隐藏或被覆盖的声明被排除在使用声明器引入的声明集之外。

因此,对于模板参数和返回类型,方法都被隐藏在以下场景中:

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() {}
};

Demo (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) {}
};

Demo

票数 8
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/14600201

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档