不确定如何使用enable_if以及为什么它很重要



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

例如,如果类型Tint,以下内容是否意味着应bool返回类型?

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

要理解这一点,我们必须深入研究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.

在正常代码中,这将是一个编译错误,但这是模板和"替换失败不是错误"。换句话说;如果某些东西被替换成有效的代码,编译器会很高兴,忘记所有没有的东西。

嘿,另一个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.

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

简而言之: 如果模板声明中模板参数的扩展导致错误,编译器不会停止或发出错误消息或警告,编译器将简单地忽略声明和以下定义。

如果条件为 falseenable_if将导致错误。

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

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 检查您的类型是否能够使用迭代器,并且您还可以专门化一种无需此类迭代器即可进行 aceing 的方法。仅作为示例!

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

它允许在编译时使用一些常量操作东西。我的一个类的例子:

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

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

参考和示例在这里。

相关内容

  • 没有找到相关文章

最新更新