不确定enable_if的使用方式及其重要性

时间:2019-08-07 07:40:23

标签: c++ sfinae enable-if

从一些网站上阅读后,我认为enable_if允许我们在条件为真的情况下启用或限制类型?我不太确定,有人可以澄清一下到底是什么吗?我也不确定如何使用它以及在什么情况下它可能是相关的。我还看到了可以使用其模板参数的各种方式,这进一步让我感到困惑。可以使用多少种方式?

例如,以下内容是否意味着如果类型boolT,则返回类型应该是int

typename std::enable_if<std::is_integral<T>::value,bool>::type
  is_odd (T i) {return bool(i%2);}

3 个答案:

答案 0 :(得分:4)

要了解这一点,我们必须深入SFINAE或“替换失败不是错误”。这是一个有点难以理解的原理,也是许多编译时模板技巧的核心。

让我们举一个非常简单的例子:

#include <iostream>

struct Bla {
    template <typename T,
        std::enable_if_t<std::is_integral<T>::value, int> = 0
    >
        static void klaf(T t)
    {
        std::cout << "int" << std::endl;
    }

    template <typename T,
        std::enable_if_t<std::is_floating_point<T>::value, int> = 0
    >
        static void klaf(T t)
    {
        std::cout << "float" << std::endl;
    }
};

int main()
{
    Bla::klaf(65);
    Bla::klaf(17.5);
}

打印:

int
float

现在,这是如何工作的?好吧,在Bla::klaf(65)的情况下,编译器会找到两个与名称匹配的函数,然后在名称查找结束后,它会尝试通过替换类型来选择最佳的函数(重要提示:名称查找首先发生并且只有一次,然后替换。)

这发生在替换中(第二更有趣):

template <typename T, std::enable_if_t<std::is_floating_point<T>::value, int> = 0>
  static void klaf(T t) {...}

-> T becomes int

template <int, std::enable_if_t<std::is_floating_point<int>::value, int> = 0>
  static void klaf(int t) {...}

-> is_floating_point<int>::value evaluates to false

template <int, std::enable_if_t<false, int> = 0>
  static void klaf(int t) {...}

-> enable_if_t<false,... evaluates to nothing

template <int, = 0>
  static void klaf(int t) {...}

-> the code is malformed: ", = 0" does not make sense.

在常规代码中,这将是编译错误,但这是模板,并且“替换失败不是错误”。换一种说法;如果 something 替换为有效代码,编译器会感到高兴,而忘记了所有没有的东西。

嘿,另一个Bla::klaf选项实际上可以替代有效代码:

template <typename T, std::enable_if_t<std::is_integral<T>::value, int> = 0>
  static void klaf(T t)

-> T becomes int

template <int, std::enable_if_t<std::is_integral<int>::value, int> = 0>
  static void klaf(int t)

-> is_integral<int>::value evaluates to true

template <int, std::enable_if_t<true, int> = 0>
  static void klaf(int t)

-> enable_if_t<true, int> evaluates to int

template <int, int = 0>
  static void klaf(int t)

-> This is actually valid code that the compiler can swallow.

答案 1 :(得分:2)

它允许在编译时使用一些常量来操纵东西。来自我的一个班级的示例:

template <bool R>
class foo
{
 ...
    template <bool Q = R>
    typename std::enable_if<Q,void>::type
    Join()
    {
    }
 };

Join()仅在用foo<true>实例化对象时定义。它基于SFINAE。替换时编译器不会出错,它只会忽略声明。

参考文献和示例here

答案 2 :(得分:2)

enable_if只是用于实现SFINAE的小助手。 https://en.cppreference.com/w/cpp/language/sfinae

在很短的时间内: 如果模板声明中的模板参数扩展导致错误,则编译器将不会停止或发出错误消息或警告,编译器将仅忽略该声明以及以下定义。

如果条件为enable_if

false将导致错误。

一个典型的用例就是这样:

struct A{};
struct B{};

template<typename T>
struct Foo
{
    template<typename U = T>
        typename std::enable_if<std::is_same<U,A>::value>::type
        bar() { std::cout << "1" << std::endl; }

    template<typename U = T>
        typename std::enable_if<std::is_same<U,B>::value>::type
        bar() { std::cout << "2" << std::endl; }
};

int main()
{
    Foo<A>{}.bar();
    Foo<B>{}.bar();
}

为什么需要SFINAE:

如果编写通用代码,则有时需要对进入模板的类型进行一些假设。可以说,您希望获得一个容器类型,并且现在想要对其进行迭代。因此,您必须能够在模板化函数或方法内部生成迭代器。但是,如果获得其他类型,则可能无法正常工作,因为该类型没有默认的迭代器。现在,您可以简单地使用SFINAE进行检查,您的类型可以使用迭代器,并且还可以专门化没有这种迭代器的Acing方法。仅作为示例!

SFINAE是一件非常复杂的事情,容易出错。最常见的陷阱:在非推导上下文中评估模板参数!