C++17(2)

fold expression

  • 虽然C++11引入了可变参数,但是没有引入对可变参数进行操作的语言支持。所以在C++17之前,我们只能使用旧的语言特性来解决。
  • 我们常使用递归和初始化来解决可变参数的解包问题。
void print(){}  //处理边界

template <class First, class ... Rest>
void print(First first_arg, Rest...rest_args){
	cout << first_arg;
	print(rest_args...); //将参数包传递
}

  • 上面是经典的First-Rest技巧(据说是meyers大神起的名字。。。)
  • 将一个参数包分解成1+N,然后处理那个1,递归处理N。
  • 最终我们需要一个简单的版本来处理边界。

逗号表达式

template <class...Args>
void print(Args...args){
	std::initializer_list<int>{((cout << args), 0)...};
}
  • 这里也可以将initializer_list换成数组,或者lambda表达式。但是我最喜欢使用这个。

C++17 && later

template <class...Args>
void print(Args...args){
	(cout << ... << args); //折叠表达式
}
  • 上面就是折叠表达式的一种情况。
  • 折叠表达式一共有4种形式,

折叠表达式

  • 其中I是参数包为空包时的默认初始值。
  • 注意,小括号也是折叠表达式的一部分。
template <class ... Args>
auto u_minus_left(Args ... args) { //一元左折叠
	return (... - args);   //括号是折叠表达式的一部分 !!!
}

template <class ...Args>
auto u_minus_right(Args ... args) { //一元右折叠
	return (args - ...);   //需要括号!!
}



u_minus_left(1, 2, 3); // ((1 - 2) -3 ) == -4
u_minus_left();        // error !

u_minus_right(1, 2, 3); // (1 - (2 - 3)) == 2
u_minus_right();       //error !
  • 可以看到左折叠和右折叠的解包方式是不同的。
  • 一元折叠表达式不支持空包。
  • 但是有3个运算符可以。 && , ||, 逗号表达式。
  • &&空包时为true, || 为false, 逗号表达式为void()。
template <class ... Args>
auto b_minus_left(Args ... args) { //二元左折叠
	return (0 - ... - args);
}

template <class ...Args>
auto b_minus_right(Args ... args) { //二元右折叠
	return (args - ...- 0);
}

b_minus_left(1, 2, 3); // (((0 - 1) - 2) - 3) == -6
b_minus_left();        // 0 , ok

b_minus_right(1, 2, 3); // (1 - (2 - (3 - 0))) == 2
b_minus_right();       //0 ,ok
  • 可以看到二元折叠表达式的结果可能和一元的完全不同!
  • 二元折叠表达式有初始值,当为空包时,匹配初始值。

constexpr if

  • 这也是我比较喜欢的C++17特性之一。
  • 在上面我们的递归版本的print例子中,我们必须使用普通版本的print来结束递归。我们无法使用if-else语句来实现分流。
template<class First, class ... Rest>
void print(First first_arg, Rest...rest_args){
	if(sizeof...(rest_args) == 0) //error !
		cout << first_arg;
	else
	print(rest_args...);
}

print(3);
  • 但是这不起作用,因为if-else是运行期的代码。
  • 编译器尝试去实例化的时候先去实例化void print(),然后发现你去调用了void print(),编译器就又去寻找该函数,但是你没有该函数。编译器不会考虑if语句。
  • 在C++17我们引入了if constexpr,编译器if表达式。
template<class First, class ... Rest>
void print(First first_arg, Rest...rest_args){
	if constexpr(sizeof...(rest_args) == 0) 
		cout << first_arg;
	else					//必要的else !!!
	print(rest_args...);
}

print(3);  //ok
  • if constexpr表达式的条件必须是编译期条件。
  • 这里要注意的是,最好将所有函数语句都包进if constexpr-else语句,不要有另外的语句,否则可能引发错误。
template<class First, class ... Rest>
void print(First first_arg, Rest...rest_args){
	if constexpr(sizeof...(rest_args) == 0) 
		cout << first_arg;
						//没有else,危险!!
	print(rest_args...);  //这条语句独立于if constexpr之外!!
}
  • 这样会导致出现这样的一个函数,
void print(First first_args, /*空包*/){
		cout << first_arg;
		print();  //这条语句独立于if constexpr之外!!
}
  • 仍然导致递归失败。

if constexpr更多的作用:

  • 我们使用if-constexpr可以实现一个函数拥有不同类型的返回值。
template <class T>
constexpr auto type_value() { //return decltype(T::value) or string
	if constexpr (T::value)
		return T::value;
	else     //不可缺少的else !否则return string就会被两个函数共享!!
		return std::string("T::value == 0");
}

struct test_struct {
	static constexpr int value = 1;
};
struct test_struct_string {
	static constexpr int value = 0;
};
int main() {
	cout << typeid(type_value<test_struct>()).name();
	cout << typeid(type_value<test_struct_string>()).name();
	return 0;
}
  • 我们使用if constexpr实现了一个函数多个返回值。
  • type_value()在编译期实际上会被分成两个函数,
/*
	template <class T>
constexpr auto type_value() { 
		return T::value;
}

template <class T>
constexpr auto type_value() { 
		return std::string("T::value == 0");
}
*/
  • 然后根据if constexpr的条件来决定调用哪个函数。
    - 如果if constexpr的条件为false,那么该部分就会被丢弃,不会被实例化。
  • 这里我们也可以看出else的不可缺少,否则两个函数都会有对string的return表达式。

more:

  • 实际上,if constexpr还有更多有用的地方。

  • 这与type_traits关系匪浅。为什么我们会有type_traits?因为我们想要在编译期间知道更多的信息,好用来帮助我们进行代码分流。

  • 我使用经典的advance来举栗子。

  • advance函数,接受一个迭代器iterator,一个整数n,表示将该迭代器前进(也有可能后退,取决于n的正负)n个长度。

  • 但是,不同的迭代器,前进的方式不同。我想对不同的迭代器采取不同的应对方式,这样就能增加效率。

template <class InputIterator, class Distance>
  void advance (InputIterator& it, Distance n){
  typename std::iteraotr_traits<InputIterator>::iterator_catagory c;
  _advance(it, n, c);
}

template <class InputIterator, class Distance>
void _advance(InputIterator& it, Distance n, random_access_iterator_tag){
	it += n;
}

template <class InputIterator, class Distance>
void _advance(InputIterator& it, Distance n, bidirectional_iterator_tag){
	if(n > 0){
		while(n-- != 0)
		++it;
	}
	else{
		while(n++ != 0)
		--it;
	}
}

template <class InputIterator, class Distance>
void _advance(InputIterator& it, Distance n, forward_iterator_tag){
	while(n-- != 0){
		++it;
	}
}
  • 我们的手法就是对iterator进行分类,利用函数重载来解决不同的迭代器类型。
  • 但是我们有了if constexpr后,就可以更简单的实现。
template <class InputIterator, class Distance>
  void advance (InputIterator& it, Distance n){
  using t = typename iterator_tratis<InputIterator>::iterator_catagory;
	if constexpr(std::is_same_v<t, random_access_iterator_tag>){
		it += n;
	}
	else if constexpr(std::is_same_v<t, bidirectional_iterator_tag>){
		if(n > 0){ //双向迭代器
		while(n-- != 0)
		++it;
	}
	else{
		while(n++ != 0)
		--it;
	}
	}

	else{ //单向迭代器
			while(n-- != 0){
		++it;
	}
	}
}
  • VS2019下的advnace正是使用的if constexpr重写了,有兴趣的可以查看。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值