《深入C++11 代码优化与工程应用》勘误表(一)

这本书其实谬误还不少,我网上找了的确提到了一些勘误但还是有些漏网之鱼。C++11从标准发布以来都过去10年以上了,看此书目的无外乎是11是后续版本的基础,并且我工作鲜少真正手写C++,巩固一下还是很有必要的。况且《深入C++11》提到了很多模板元编程的例子,现代C++其实要掌握好感觉对模板提出的更高的要求。
在此就开个也许会更新待续的勘误表(当然这是自己发现的):

第3章

1. 使用辅助函数find_index,其指定的模板参数不对,应该是:

template<typename T, typename... Args>
int find_index(tuple<Args...> const& t, T&& val)
{
	return detail::find_index<sizeof...(Args), T, Args...>::call(t, forward<T>(val));
}

sizof...(Args)表示可变模板参数的个数,其实也就是编译器知道tuple的元素个数,传给detail::find_index匹配模板参数I,通过I - 1来指代tuple的索引。

namespace detail
{
    ...
	template<int I, typename T, typename... Args>
	struct find_index {
		static int call(const tuple<Args...>& t, T&& val) {
			return compare(get<I - 1>(t), val) ? I - 1 : find_index<I - 1, T, Args...>::call(t, forward<T>(val));
		}
	};

	template<typename T, typename... Args>
	struct find_index<0, T, Args...> {
		static int call(const tuple<Args...>& t, T&& val) {
			return compare(get<0>(t), val) ? 0 : -1;
		}
	};
}

2. 使用逐步展开参数包的方式,在运行期根据索引获取索引所在元素,其代码:

template<typename Arg>
void GetArgByIndex(int index, tuple<Arg>& tp) {
	cout << get<0>(tp) << endl;
}

template<typename T, typename... Args>
void GetArgByIndex(int index, tuple<T, Args...>& tp) {
	if (index < 0 || index >= tuple_size<tuple<T, Args...>>::value)
		throw invalid_argument("index is not valid");
	if (index > 0) {
		GetArgByIndex(index - 1, (tuple<Args...>&) tp);
	}
	else
		cout << get<0>(tp) << endl;
}

void TestTuple1() {
	tuple<int, double> tp = make_tuple(1, 2.1);
	const size_t length = tuple_size<tuple<int, double>>::value;
	for (size_t i = 0; i < length; ++i) {
		GetArgByIndex(i, tp);
	}
}

意为通过递归+终止函数逐项展开参数包,把运行期的变量i/index和“现在传入tp的类型是啥”关联起来。比如index为1,传入的tp类型是tuple<int ,double>; 然后递归调用index为0,tp类型是tuple<double>;终止条件是tuple<Arg..>模版参数个数为1。

但是这段代码在vs2015也行,其他编译器如gcc报错:

它认为T = double, Args = <>也能匹配template<typename T, typename... Args>版本的GetArgByIndex从而导致ambiguous错误!我在template<typename T, typename... Args>版本函数返回值加了编译期抉择让他对T = double, Args = <>这种情况不生效:

template<typename T, typename... Args>
typename std::enable_if<(sizeof...(Args)>0)>::type
GetArgByIndex(int index, tuple<T, Args...>& tp) {
	...
}

这样就可以了。

3. 遍历tuple的思路是先将tuple展开为可变参数模板,再用展开可变参数模板的方法来遍历tuple。其中代码:

namespace details
{
	...
	template<typename Func, int... Indexes, typename... Args>
	void for_each_helper(Func&& f, IndexTuple<Indexes...>, tuple<Args...>&& tup) {
		for_each_impl(forward<Func>(f), forward<Args>(get<Indexes>(tup))...);
	}
} // namespace details

template<typename Func, typename Tuple>
void tp_for_each(Func&& f, Tuple& tup)
{
	using namespace details;
	for_each_helper(forward<Func>(f), typename make_indexes<tuple_size<Tuple>::value>::type(), tup);//compile err
}

tup编译有误。因为void for_each_helper(Func&& f, IndexTuple<Indexes...>, tuple<Args...>&& tup)里面的 tuple<Args...>&&不是万能引用,而是个右值。只有形如T&&,设计模板推导才是万能引用。因此实参tup这个左值无法绑定到右值。和下列code原理是一样的:

	int lval = 1;
	int&& rval1 = lval;//compile err, rvalue reference to type 'int' cannot bind to lvalue of type 'int'
	int&& rval1 = forward<int&>(lval);//compile err,rvalue reference to type 'int' cannot bind to lvalue of type 'int'

于是乎,我只能使用std::move或std::forward<Tuple>将其转为右值:

	for_each_helper(forward<Func>(f), typename make_indexes<tuple_size<Tuple>::value>::type(), forward<Tuple>(tup));

int&& rval1 = forward<int>(lval);

至于为什么forward可以把tup这个左值转化为右值,forward不是完美转发左值或者右值吗?我们很容易被语言本身所误解,forward<T>一般需要T和传入的实参一起work以发生引用折叠,即:

template<typename T>
void f(T&& fParam) {
	anotherFunc(forward<T>(fParam));
}

由于T&&是个万能引用,所以调用f(fParam)的时候,T会进行模板推导,把param就是个左值还是右值已经编码进去了。比如fParam是int&,那么T是int&;fParam是int&&,那么T是int&&;fParam是int,那么T是int。forward所做的事情不过是当且仅当T中的信息表明传递给实参是个右值,即T的推导结果型别是个非引用型别时,对 fParam(左值)实施到右值的强制型别转换。

我们这边forward<Tuple>(tup)中的Tuple和forward<int>(lval)中的int一样,T没有被推导直接指定成了一个非引用型别(int,不是int&),于是tup和lval尽管本身是左值却被转换为了右值。这时作用和move没有区别。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值