SFINAE学习

基本的模板运用

本节内容来源

例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

posted @ 2023-07-12 20:44  DaoDao777999  阅读(16)  评论(0编辑  收藏  举报