📜  C++ sfinae - C++ (1)

📅  最后修改于: 2023-12-03 15:13:54.926000             🧑  作者: Mango

C++ SFINAE (Substitution Failure Is Not An Error)

在C++中,SFINAE (Substitution Failure Is Not An Error) 是一种技术,可以帮助我们实现部分特化和重载函数,在编译器构建模板实例时排除某些类型,并且仍然保持编译过程的平稳。

在这种情况下,编译器并不认为模板出现了错误,而只是“在候选模板中差异很小”,因此模板被“剔除”在外,候选模板的匹配将会考虑其他可用模板。

什么是SFINAE?

SFINAE 是一个术语,用于描述编译器模板解析行为的一部分。通常,如果模板无法合法地应用于某些类型,则使用SFINAE覆盖异常处理机制,从而使编译器可以将该模板排除在匹配策略外,从而尝试其他备选模板。

为什么需要SFINAE?

在C++中,宏替换和命名约定已经足够灵活,可以轻松地处理许多相对简单的问题。但随着函数和类的数目不断增加,程序员不得不更多地依赖于超载技术和各种其他技术。这导致一些问题,例如重载决策,部分特化等等。

SFINAE的优点是,在编译时排除不适用于特定类或类型的功能。这使代码更清晰,更易于维护,并且可以使代码更加高效。

SFINAE的简单使用

一个简单的例子管有一个类 check_type

template<typename T>
class check_type
{
  struct yes {char c;};
  struct no {char c[2];};

  template<typename C>
  static yes test(typename C::type*);

  template<typename C>
  static no test(...);

public:
  static const bool value = (sizeof(test<T>(0)) == sizeof(yes));
};

上面代码中, struct yesstruct no 分别提出两种类型, 然后提出了两种函数 test(), 如果一个类型定义了 type,那么 那么 test() 获取 C的类型经过提取之后是 yes,否则它就是 no 。

这样在定义 check_type 类时就可以测试那个其中某个对象可以访问 type ,具体做法如下:

class A {public: typedef int type;};
class B {};

// Check if A and B have type members
int main()
{
  std::cout << std::boolalpha;
  std::cout << check_type<A>::value << std::endl; // true
  std::cout << check_type<B>::value << std::endl; // false
  return 0;
}
SFINAE的高级使用

一个常见的用例是通过 SFINAE 剔除一些过期功能,以保持代码更加清晰和密集。下面的例子说明了我们如何使用SFINAE和std :: enable_if的联合使用来使编译器选择特定的函数,即使该函数的类型参数不完全匹配。

#include <type_traits>

template<typename T>
typename std::enable_if<!std::is_integral<T>::value, void>::type
foo(T t)
{
    //...
}

template<typename T>
typename std::enable_if<std::is_integral<T>::value, void>::type
foo(T t)
{
    //...
}

上面的例子中,我们定义了两个函数 foo,它们不完全具有相同的参数类型。因为这些模板都具有可转换的模板参数,并且因为 enable_if 通过向函数稍微添加一些优势来构成更复杂的功能,这些模板可以特化为非常精细的方式。

template< class T >
typename std::enable_if<
    std::is_integral<T>::value && sizeof(T) < 4, 
    T
>::type
increment(T t)
{
    return (T)(++t);
}

template< class  T>
typename std::enable_if<
    std::is_floating_point<T>::value, 
    T
>::type
increment(T t)
{
    return ++t;
}

上面的例子中, 我们定义了两个函数 increment(),在 std::is_integral 类型中,它仅作用于大小超过4的类型,而对于浮点型来说,则没有这种限制。

SFINAE的局限性

SFINAE可以在编译器构建模板实例时排除某些类型,并且仍然保持编译过程的平缓。这使得其在设计C ++ 模板时非常有用。但是,SFINAE还有一些局限性值得注意:

  1. 程序员需要熟悉C ++ 模板机制,这些机制常常比常规C ++ 代码更加复杂和晦涩。

  2. SFINAE不适合解决所有问题。当出现复杂条件检查和部分特化时,SFINAE往往不能提供足够的支持。

  3. SFINAE代码通常很难阅读和理解,这是因为其复杂性和晦涩,因此程序员需要谨慎地使用它。

结论

虽然某些C ++ Only-features看起来很复杂,但它们通常可以帮助我们解决更复杂的问题,并保持我们的代码整洁和简单。SFINAE既是其中一种,它使我们能够编写C ++ 模板,这些模板可以轻松地处理类型驱动的情况,并且为我们提供了更高效的代码和设计能力。在程序员了解了SFINAE的原理和代码的局限性之后,它应该成为程序员组成C ++ 工具箱的一个有力工具之一。