boost foreach 探究

short array_short[] = {1, 2, 3};

BOOST_FOREACH(short &i, array_short)

{

i = 4 - i;

}

BOOST_FOREACH(into i, array short)

std::cout << i << std::endl;

into array_int[][2] = {1, 2, 3, 4};

BOOST_FOREACH(into (&row)[2], array_int)

BOOST_FOREACH(into i, row)

std::cout << i << std::endl;

std::list get_file_list();

BOOST_FOREACH(std::string const &i, get_file_list())

std::cout << i << std::endl;

这项神奇的魔法是怎么工作的?

FOREACH必然跟手写循环一样,内部需求也无非是对container的begin,end,iterator的自增,解引用等;

#define FOREACH(item, container) /

??? iter = (container).begin();

问题出来了,我们并不知道iter的类型,但是(container).begin()的返回类型是确定的,为什么C++不允许我们像这样申明变量auto iter = (container).begin();相同的类型为什么要在表达式两边各出现一次?

当然这难不倒我们,记得模版参数推导么,在其中不就有类型参数

template

void f(T const & iter) {}

template

struct X {

X(T cosnt & iter) item(iter) {};

T &item;

}

类型可以捕获到,但是我们如何保存,模版类的类型必然要带上其模版参数才显得完整,依然没能绕开此问题。

C++比人们想象的都强大,只要有想象力,必然可以实现。比如C++的GP的能力是被发现的而非发明的。Andrei Alexandrescu这一伟大的天才带给我们的总是新选”灵异”的思路,此处也一样。

被其成为诡计的ScopeGuard trick:

有类型擦出的影子

struct auto_any_base {};

template

struct auto_any : auto_any_base

{

auto_any(T const & t) : item(t) {}

mutable T item;

};

当利用模版函数返回了一个类型参数的外覆器(wrapped)(这里指auto_any是T的wrapped),通过基类的引用扩展其返回值(临时对象)的生存周期(ScopeGuard)。

现在可以完成第一部分了:

template

auto_any begin(Container const & c)

{

return c.begin();

}

#define FOREACH(item, container) /

auto_any_base const & iter = begin(container);

现在有了begin的信息,但是其类型事实上是消失了,iter其实没什么用,除非施加以个显示的类型转换来恢复其类型信息:

template

T & auto_any_cast(auto_any_base const & base)

{

return static_cast const &>(base).item;

}

template

void next(auto_any_base const & iter, Container const&)

{

++auto_any_cast(iter);

}

现在FOREACH可以有:

#define FOREACH(item, container) /

auto_any_base const & iter = begin(container); /

... /

next(iter, container);

其中next使用iter,并加上container作为参数,去推导类型Container,以此参数推导出iter原来的类型,然后就可以访问保存的迭代器,并对其自增。

(注意这其中的主线其实就是Container类型的传递)

其实到这里已经基本可以实现一个FOREACH的原始版本了,以Container类型作为主线类似的实现end等:

template

auto_any end(T const& t)

{

return t.end();

}

结束条件显然就是当迭代器到达end的时候:

template

bool done(auto_any_base const& cur, auto_any_base const& end, T const&)

{

typedef typename T::const_iterator iter_type;

return auto_any_cast(cur) == auto_any_cast(end);

}

同时注意到我们的目的是以item来访问容器中的元素,所以必然对迭代器有一个解引用的动作:

template

typename T::const_reference deref(auto_any_base const& cur, T const&)

{

typedef typename T::const_iterator iter_type;

return *auto_any_cast(cur);

}

一切就绪了,是时候将其组装起来:

#define FOREACH (item, container) /

if(auto_any_base const& b = begin(container)) {} else /

if(auto_any_base const& e = end(container)) {} else /

for(bool more = true; /

more && !done(b,e,container); /

next(b,container)) /

if (more = false) {} else /

for(item = deref(b,container); !more; more = true)

这里出了一推奇怪的if else 之外,其他的都很容易理解,这if else的作用嘛,自然是为了使得FOREACH的特性类似于其本意,也就是这些封装必须得保证是一条串行的单语句,并且其内部的状态不能扩散到外面去,所以就写成了这样的一些串行的语句,为使其一条一条的执行,只需给auto_any_base一个自定义的类型转化:

struct auto_any_base

{

operator bool() const {return false;}

};

似乎一切都已结束,其实才刚刚开始。

对于一半的容器如vector之类的可以工作良好,当时注意到我们定义的是一个宏,对FOREACH来说,container可以是各种各样的容器,可以是各种各样的形式,比如前面看到的

get_file_list(),函数调用返回标准容器,然而预处理期只有符号替换,没有值传递,所以在container所有出现的地方都会被替换为get_file_list(),于是该函数被多次求值,其危害性自然不言而喻。第一反映应该是效率问题,除此此外如果函数本身是有副作用的,一切将失去控制。同时,大多数地方我们并不需要其返回值,而只需要返回值的类型(见next,done),所以必然有更好的实现方式。

Night of the Living Dead Code

我们已经熟悉了通过外覆器操纵型别的方法,但是否有一种方法可以获得表达式的型别同时却又不计算表达式,是的!就是那神奇的

?:(conditional operator),其在标准中的定义允许我们实现这一诡计。

// a simple type wrapper

template struct type2type {};

// convert an expression of type T to an

// expression of type type2type

template

type2type encode_type(T const & t)

{

return type2type();

}

// convertible to type2type for any T

struct any_type

{

template

operator type2type() const

{

return type2type();

}

};

// convert an expression of type T to an

// expression of type type2type without

// evaluating the expression

#define ENCODE_TYPEOF(container) /

(true ? any_type() : encode_type(container))

对于一个表达式expr,ENCODE_TYPEOF(expr)得到的是type2type,并且expr并没有被计算,显然这令人费解。

一个b?x:y 首先是一个表达式,所以必然有一个型别,她难以琢磨,型别同时依赖于她的第二个(x)和第三个(y)表达式,要弄清除她的返回型别对编译期设计者来说是一个足够头痛的问题,在标准中,也用了一页半来描述她。然而要弄清除ENCODE_TYPEOF还是比较容易的,如果X与Y中有一个是class type,并且X与Y不一致,编译期将检查X是否能够转化到Y,或者Y是否能够转化到X,如果转化是非歧义的,将采用这个转化后的型别,还是直接列上原话比较好:

Otherwise, if the second and third operand have different types, and either has (possibly cv-qualified) class type, an attempt is made to convert each of those operands to the type of the other. The process for determining whether an operand expression E1 of type T1 can be converted to match an operand expression E2 of type T2 is defined as follows:

— If E2 is an lvalue: E1 can be converted to match E2 if E1 can be implicitly converted (clause 4) to the type “reference to T2”, subject to the constraint that in the conversion the reference must bind directly(8.5.3) to E1.

现在自定义了any_type到type2type的转换,第一个表达式为true,所以选择第一个分支,第二个分支永远不会被计算,是dead code,但他却如幽灵般的存在,并且改变了表达式的型别。现在剩下的就是修改以前需要引用container的地方,改为type2type就好了,比如:

template

void next_(auto_any_base const & iter, type2type)

{

typedef typename Container::const_iterator citer_type;

++auto_any_cast(iter);

}

现在FOREACH可以变为类似这样

#define FOREACH(item, container) /

auto_any_base const & iter = begin(container); /

... /

next(iter, ENCODE_TYPEOF(container)); /

问题依然没有结束,如get_file_list()这样的函数调用返回值以一个右值,在分号过后其作用生命周期结束,指向其的引用就没有意义,同时STL容器的拷贝代价是高昂的,在非必要的情况下我们并不想拷贝他,所以必然要能够检测左值与右值,以此区分,并决定是否要拷贝。假设我们有一个宏EVAL(expr,is_rvalue)可以检测左值和右值,并且结果反映到is_rvalue中,继续完善FOREACH。

对于左值,可以直接范围引用,对于右值,需要拷贝后返回。需要一个类似union一样的东西,boost::variant是理智的选择。

// returns to Container or a Container*

template

auto_any >

contain(Container const & t, bool const & is_rvalue)

{

// Return either a Container or a Container* depending on whether

// the container is an rvalue or not

typedef boost::variant variant_t;

return is_rvalue ? variant_t(t) : variant_t(&t);

}

一切都便了,不过思路不变,只需依次改写:

template

auto_any

begin(auto_any_base const & container, bool is_rvalue, type2type)

{

typedef boost::variant variant_t;

variant_t & var = auto_any_cast(container);

// Extract either a Container or a Container* depending on whether

// the container is an rvalue or not

Container const & c = is_rvalue ? boost::get(var)

: *boost::get(var);

return c.begin();

}

template

auto_any

end(auto_any_base const & container, bool is_rvalue, type2type)

{

typedef boost::variant variant_t;

variant_t & var = auto_any_cast(container);

Container const & c = is_rvalue ? boost::get(var)

: *boost::get(var);

return c.end();

}

template

bool done(auto_any_base const & cur, auto_any_base const & end, type2type)

{

typedef typename Container::const_iterator citer_type;

return auto_any_cast(cur) == auto_any_cast(end);

}

template

typename Container::const_reference deref(auto_any_base const & cur, type2type)

{

typedef typename Container::const_iterator citer_type;

return (*auto_any_cast(cur));

}

FOREACH大概会是这样的:

//

// FOREACH

#define FOREACH(item, container) /

if (bool is_rvalue = false) {} else /

if (auto_any_base const& c = contain(EVAL(container, is_rvalue), is_rvalue)) {} else /

if (auto_any_base const& b = begin(c, is_rvalue, ENCODE_TYPEOF(container))) {} else /

if (auto_any_base const& e = end(c, is_rvalue, ENCODE_TYPEOF(container))) {} else /

for (bool more = true; /

more && !done(b, e, ENCODE_TYPEOF(container)); /

more ? next_(b, ENCODE_TYPEOF(container)) : (void)0) /

if (more = false) {} else /

for(item = deref(b, ENCODE_TYPEOF(container)); !more; more = true)

现在可以处理右值,并且container只会求值一次,剩下的就是EVAL的实现问题了。

一个100%标准的做法在VC下居然不能正常运行,冏了。

template

struct rvalue_probe

{

rvalue_probe(T & t, bool& b)

: value(t)

, is_rvalue(b)

{}

operator T()

{

this->is_rvalue = true;

return this->value;

}

operator T &() const

{

return this->value;

}

T& value;

bool& is_rvalue;

};

template

rvalue_probe make_probe(Container & t, bool & is_rvalue)

{

return rvalue_probe(t, is_rvalue);

}

template

rvalue_probe make_probe(Container const & t, bool & is_rvalue)

{

return rvalue_probe(t, is_rvalue);

}

#define EVAL(expr,is_rvalue) (true? make_probe(expr,is_rvalue) : expr)

我觉得不可思议,于是翻开boost的源码,发现果然是这样的,太尴尬了

///

// Detect at run-time whether an expression yields an rvalue

// or an lvalue. This is 100% standard C++, but not all compilers

// accept it. Also, it causes FOREACH to break when used with non-

// copyable collection types.

///

下面是一个替代做法

///

// Detect at compile-time whether an expression yields an rvalue or

// an lvalue. This is rather non-standard, but some popular compilers

// accept it.

///

template

struct rvalue_probe

{

operator T()

{

return *reinterpret_cast(this);

}

operator T&() const

{

return *reinterpret_cast(const_cast(this));

}

};

template

inline rvalue_probe const make_probe(T const &)

{

return rvalue_probe();

}

template

false_* is_rvalue_(T &, int)

{

return 0;

}

template

true_ * is_rvalue_(T const &, ...)

{

return 0;

}

is_rvalue_的第二个参数是区分const的左值和右值的关键。

// A sneaky way to detect at compile-time whether an expression yields

// an rvalue or an lvalue. This is rather non-standard, but vc accept

// this rather than the 100% statand way. sigh!

#define IS_RVALUE(expr) /

(true ? 0 : _foreach_detail::is_rvalue_((true ? _foreach_detail::make_probe(expr) : (expr)), 0))

然后就是将一切组装起来的时候了

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值