IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 说说SFINAE -> 正文阅读

[C++知识库]说说SFINAE


大后天就是除夕了,牛年将过,虎年马上来了。以一篇关于C++的非常小众的文章作为牛年的结尾,若有讲的不清楚或讲错的地方欢迎大家留言指出来。为什么今年最后一篇文章是关于c++的呢?因为自己工作后用过的第一门语言便是c++,感谢。

SFINAE简介

SFINAESubstitution Failure is NOT an Error的首字母缩写,David Vandevoorde最先使用这个缩写来描述相关编程技术,可翻译为替换失败并非错误

维基百科中对此有讲解,举的例子也不错,可点击这里查看,本文关于SFINAE的例子来源于此百科。可是,今天有朋友反应看不太懂这个例子了,相信很多没有使用过c++11或者更高版本的朋友多少都有此种困惑,这里我们简单解说一下。

言归正传,当创建一个重载函数的候选集时,某些(或全部)候选函数是用模板实参替换(可能的推导)模板形参的模板实例化结果。如果某个模板的实参替换时失败,编译器将在候选集中删除该模板,而不是当作一个编译错误从而中断编译过程,这需要C++语言标准授予如此处理的许可。如果一个或多个候选保留下来,那么函数重载的解析就是成功的,函数调用也是良好的。

SFINAE的一个例子

通过一个简单例子了解一下SFINAE,

struct Test {
  typedef int foo;
};

template <typename T>
void f(typename T::foo) {}  // Definition #1

template <typename T>
void f(T) {}  // Definition #2

int main() {
  f<Test>(10);  // Call #1.
  f<int>(10);   // Call #2. Without error (even though there is no int::foo)
                // thanks to SFINAE.
}

在限定名字解析时(T::foo)使用非类的数据类型,导致f<int>推导失败,因为int并无嵌套数据类型foo, 但程序仍是良好定义的,因为候选函数集中还有一个有效的函数(Defination #2)!

虽然SFINAE最初引入时是用于避免在不相关模板声明可见时(如通过包含头文件)产生不良程序。许多有心的程序员后来发现这种行为可用于编译时内省introspection。具体说来,在模板实例化时允许模板确定模板参数的特定性质。

例如,SFINAE用于确定一个类型是否包含特定typedef

#include <iostream>

template <typename T>
struct has_typedef_foobar {
  // Types "yes" and "no" are guaranteed to have different sizes,
  // specifically sizeof(yes) == 1 and sizeof(no) == 2.
  typedef char yes[1];
  typedef char no[2];

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

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

  // If the "sizeof" of the result of calling test<T>(nullptr) is equal to
  // sizeof(yes), the first overload worked and T has a nested type named
  // foobar.
  static const bool value = sizeof(test<T>(nullptr)) == sizeof(yes);
};

struct foo {
  typedef float foobar;
};

int main() {
  std::cout << std::boolalpha;
  std::cout << has_typedef_foobar<int>::value << std::endl;  // Prints false
  std::cout << has_typedef_foobar<foo>::value << std::endl;  // Prints true
}

当类型T有嵌套类型foobartest的第一个定义被实例化并且空指针常量被作为参数传入。(结果类型是yes。)如果不能匹配嵌套类型foobar,唯一可用函数是第二个test定义,且表达式的结果类型为no。省略号(ellipsis)不仅用于接收任何类型,它的转换的优先级是最低的,因而优先匹配第一个定义,这去除了二义性。

使用C++11进行代码简化

在 C++11 中,上述代码可以简化为:

#include <iostream>
#include <type_traits>

template <typename... Ts>
using void_t = void;

template <typename T, typename = void>
struct has_typedef_foobar : std::false_type {};

template <typename T>
struct has_typedef_foobar<T, void_t<typename T::foobar>> : std::true_type {};

struct foo {
  using foobar = float;
};

int main() {
  std::cout << std::boolalpha;
  std::cout << has_typedef_foobar<int>::value << std::endl;
  std::cout << has_typedef_foobar<foo>::value << std::endl;
}

对于c++11代码的解说

  1. 要看懂上边的代码,首先要知道的第一点是std::false_typestd::true_type都是结构体,这两个结构体有bool型的成员变量,值分别为falsetrue,以下是其可能的实现。
typedef integral_constant<bool, false> false_type;
typedef integral_constant<bool, true> true_type;
template<class T, T v>
struct integral_constant {
    static constexpr T value = v;
    using value_type = T;
    using type = integral_constant; // using injected-class-name
    constexpr operator value_type() const noexcept { return value; }
    constexpr value_type operator()() const noexcept { return value; } // since c++14
};
  1. 第二点就是在c++中结构体也是可以继承的,所以has_typedef_foobar完全继承false_type或者true_type,此外什么也没做。
  2. 为了便于理解,我们把上边的代码在编译器看来会是什么样子,写在下边
#include <iostream>
#include <type_traits>

template <typename... Ts>
using void_t = void;

template <typename T, typename = void>
struct has_typedef_foobar : std::false_type {};

template<>
struct has_typedef_foobar<int, void> : public std::integral_constant<bool, false>
{
};

template<>
struct has_typedef_foobar<foo, void> : public std::integral_constant<bool, true>
{
};

template<typename T>
struct has_typedef_foobar<T, void_t<typename T::foobar> > : public std::integral_constant<bool, true>
{
};

struct foo
{
  using foobar = float;
};

int main()
{
  std::cout.operator<<(std::boolalpha);
  std::cout.operator<<(std::integral_constant<bool, false>::value).operator<<(std::endl);
  std::cout.operator<<(std::integral_constant<bool, true>::value).operator<<(std::endl);
}
  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-01-29 22:54:07  更:2022-01-29 22:55:19 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 9:22:35-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码