首页 > 其他分享 >SFINAE学习

SFINAE学习

时间:2023-07-12 20:57:35浏览次数:40  
标签:std return invoke SFINAE auto dismantle 学习 template

基本的模板运用

本节内容来源

例1 普通模板,做类型判断然后进行分支选择

定义一个模板函数,接收类型为int则返回1,否则执行substr

template<class T>
auto func(T t) {
  if constexpr (std::is_same<T, int>::value) {
	return t + 1;
  } else {
	return t.substr(1);
  }
}
int main() {  
	int i = 0;  
	printf("%d\n",func(i));  
	std::string s="hello";
	std::cout<<func(s);
	return 0;
}

这里的else是一定要写,因为这是属于编译期判断的分支语句,如果去掉,编译期检查就是失败

std::is_same<T, int>::value

可以改写成

std::is_same<decltype(t), int>::value

但是这里有样有个问题,就是如果将参数改为

auto func(const T& t)

即使是int类型也会走下面的分支,因为此时T被推导为int,但是形参tint const&类型
此时需要带上std::decay_t<decltype(t)>,会帮你去掉这些修饰

例2 返回值判断

template<class F>
auto invoke(F f) {
  if constexpr (std::is_same_v<decltype(f()), void>) {
	f();
  } else {
	auto ret = f();
	return ret;
  }

}
int main() {
  invoke([]() {
	return 1;
  });
  invoke([]() {
	return;
  });
  return 0;
}

当我们不确定有没有返回值时,就无法确定要不要接收返回值,上述代码就会报错,因为空返回值时不可以被接收的,除此之外,还可以使用is_void_v来判定是不是void

这是在有可执行变量的情况下,如果只有可执行类型,可以使用invoke_result_t

template<class F>
auto invoke(F f) {
  if constexpr (std::is_same_v<std::invoke_result_t<F>, void>) {
	f();
  } else {
	auto ret = f();
	return ret;
  }
}

例3 自定义类型成员判断

上述都是编译期判断手段,当二者处理逻辑比较小时这样使用,如果二者处理逻辑差距较大,通常我们写几个不同的方法应对不同的情况
这是C++20才有的requires,使用requires必须保证表达式为真,否则就不参与重载决议,就会删除掉

template<class F>
requires(!std::is_same_v<std::invoke_result_t<F>, void>)
auto invoke(F f) {
  auto ret = f();
  return ret;

}
template<class F>
requires(std::is_same_v<std::invoke_result_t<F>, void>)
auto invoke(F f) {
  f();
}

当我们无法使用C++20时,可以写成这样

template<class F,
	std::enable_if_t<!std::is_void_v<std::invoke_result_t<F>>, int> = 0
>
auto invoke(F f) {
  auto ret = f();
  return ret;

}
template<class F,
	std::enable_if_t<std::is_void_v<std::invoke_result_t<F>>, int> = 0
>
auto invoke(F f) {
  f();
}

这东西叫SFINAE
可以将enable_if定义为一个宏

#define REQUIRES(x) std::enable_if_t<(x),int> = 0


template<class F,
	REQUIRES(!std::is_void_v<std::invoke_result_t<F>>)
>
auto invoke(F f) {
  auto ret = f();
  return ret;
}
template<class F,
	REQUIRES(std::is_void_v<std::invoke_result_t<F>>)
>
auto invoke(F f) {
  f();
}

还可以使用declval凭空创建对象然后使用类型捕获,适用于不求值的情况

template<class F,
	REQUIRES(std::is_void_v<decltype(std::declval<F>()())>)
>
auto invoke(F f) {
  f();
}

此处,是对返回值做一个筛选

很多时候,我们需要对模板函数传一个自定义类型

struct myclass {
  void dismantle() {
	printf("rm -rf class\n");
  }
};

struct mystudent {
  void dismantle() {
	printf("rm -rf student\n");
  }
};

struct myclassroom {
  void attack() {
	printf("attack gench\n");
  }
};
};
struct myvoid {

};

template<class T>
void gench(T t) {
  t.dismantle();
}
int main() {
  myclass mc;
  mystudent ms;
  gench(mc);
  gench(ms);
  return 0;
}

这种情况下,我们需要所传入的对象必须有对应的dismantle方法,可是有如果对象没有这个成员方法呢,那我们的处理逻辑应该变为

if(t 有 dismantle){
	t.dismantle();
	}
else{
	t.attack();
}

在C++20中,这个测试是十分方便的,使用requires可以很简单的完成

template<class T>
void gench(T t) {
  if constexpr (requires{ t.dismantle(); }) {
	t.dismantle();
  } else if constexpr (requires{ t.attack(); }) {
	t.attack();
  } else {
	printf("no any method\n");
  }

}
int main() {
  myclass mc;
  mystudent ms;
  myclassroom mcr;
  myvoid mv;
  gench(mv);
  gench(mcr);
  gench(mc);
  gench(ms);
  return 0;
}

我们可以借助这个方法判断是否具有相应成员函数

如果没有C++20则需要

template<class T>
struct has_dismantle {
  static constexpr bool value = false;
};
//特化
template<>
struct has_dismantle<myclass> {
  static constexpr bool value=true;
};
template<>
struct has_dismantle<myclassroom> {
  static constexpr bool value=false;
};

这里我们相当于直接说myclass具有这个方法,myclassroom不具有这个方法
用法:

template<class T>
void gench(T t) {
  if constexpr (has_dismantle<T>::value) {
	t.dismantle();
  } else {
	printf("no any method\n");
  }

}

这种方法比较死板,如果有个新类型,我们又要重新写一个新的特化模板
就可以配合enable_if简化实现

template<class T, class =void>
struct has_dismantle {
  static constexpr bool value = false;
};
//特化
template<class T>
struct has_dismantle<T, std::void_t<decltype(std::declval<T>().dismantle())>> {
  static constexpr bool value = true;
};

std::void_t<decltype(std::declval<T>().dismantle())>的作用就是测试表达式能否编译成功,失败的话特化就是失败,从而走上面这个模板,value就等于false

标签:std,return,invoke,SFINAE,auto,dismantle,学习,template
From: https://www.cnblogs.com/Lhh-9999/p/17548805.html

相关文章

  • (一)Git 学习之为什么要学习 Git
    一、版本控制1.1何为版本控制版本控制(Revisioncontrol)是一种在开发的过程中用于管理我们对文件、目录或工程等内容的修改历史,方便查看更改历史记录、备份,以便恢复以前的版本的软件工程技术。版本控制其实最重要的是可以记录文件的历史修改记录,从而让用户能够查看历史版本,方......
  • (二)Git 学习之基础篇
    一、理论基础1.1Git记录的是什么?Git和其它版本控制系统(如SVN)的主要差别在于Git对待数据的方式。1.1.1SVN记录差异比较从概念上来说,SVN以文件变更列表的方式存储信息,这类系统将它们存储的信息看作是一组基本文件和每个文件随时间逐步累积的差异,它们通常称作基于差异......
  • (三)Git 学习之分支操作
    一、分支简介1.1Git分支初探几乎所有的版本控制系统都会以某种形式支持分支。使用分支意味着你可以把你的工作从开发主线上分离开来,以免影响开发主线。假设你准备开发一个新功能,但是需要两周时间才能完成:第一周你写了50%的代码,如果立刻提交,由于代码还没写完,不完整的代码......
  • 二分图学习笔记
    定义对于一个无向图\(G=(V,E)\),如果存在点集\(A,B\),满足\(a\neq\varnothing\),\(b\neq\varnothing\),\(A\capB=\varnothing\),\(A\cupB=V\),且\(\forallu,v\inA\)或\(u,v\inB\),都有\((u,v)\notinE\),则称这个图是一个二分图,\(A\)称为这个二分图的左部,\(B\)称为右部。......
  • Docker学习路线3:安装设置
    Docker提供了一个名为DockerDesktop的桌面应用程序,简化了安装和设置过程。还有另一个选项可以使用Docker引擎进行安装。DockerDesktop网站Docker引擎DockerDesktopDockerDesktop是一款易于安装的应用程序,可使开发人员快速在其台式机上设置Docker环境。它适用于Windows和......
  • 【学习笔记】Tarjan
    前言:凡事都得靠自己--bobo催隔壁K8Hen天了让他写Tarjan的学习笔记,但貌似还没有动静,所以决定自己写一个。正文本文配套题单:14.图论-tarjan(强连通分量、割点、割边)前置知识熟练使用链式前向星强连通、强连通图、强连通分量的定义(详见oi-wiki,这里不再赘述)如图......
  • 5.2 随机森林在巨量数据中的增量学习
    集成学习是工业领域中应用最广泛的机器学习算法。实际工业环境下的数据量往往十分巨大,一个训练好的集成算法的复杂程度与训练数据量高度相关,因此企业在应用机器学习时通常会提供强大的计算资源作为支持,也因此当代的大部分集成算法都是支持GPU运算的(相对的,如果你发现一个算法在任何......
  • Django框架学习-Celery的使用
    celery用户文档:https://docs.celeryq.dev/en/v5.3.1/userguide/index.html1、Celery的提出用户需要在网站填写注册信息,发给用户一封注册激活邮件到邮箱,如果由于各种原因,这封邮件发送所需时间较长,那么客户端将会等待很久,造成不好的用户体验。——> 将耗时任务放到后台异步执行,从......
  • 数据结构学习2
    5、线性表的链式存储结构①定义链式存储:用一组任意的存储单元存储线性表中的数据元素。线性链表:用这种方法存储的线性表简称线性链表。特点:结点在存储器中的位置是随意的,即在逻辑上相邻的数据元素在物理上不一定相邻。实现:为了正确表示结点间的逻辑关系,在存储每个结点......
  • SRS之StateThreads学习
    最近在看SRS的源码。SRS是基于协程开发的,底层使用了StateThreads。所以为了充分的理解SRS源码,需要先学习一下StateThreads。这里对StateThreads的学习做了一些总结和记录。StateThreads是什么StateThreads是一个用户级线程库,用于多线程编程。它提供了一种轻量级的线程模型,允许开......