基本的模板运用
例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
,但是形参t
为int 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