Boost logo

Boost :

Subject: Re: [boost] [yap] Review part 1: documentation
From: Zach Laine (whatwasthataddress_at_[hidden])
Date: 2018-02-13 16:44:45


On Mon, Feb 12, 2018 at 10:13 PM, Steven Watanabe via Boost <
boost_at_[hidden]> wrote:

> AMDG
>
> On 02/12/2018 06:09 PM, Zach Laine wrote:
> > On Mon, Feb 12, 2018 at 12:08 PM, Steven Watanabe via Boost <
> > boost_at_[hidden]> wrote:
> >
> >> <snip>
> >> Unwrapping the terminals causes surprising behavior,
> >> which is only sort of fixed by automatically applying
> >> the terminal transform and making transform a no-op
> >> for non-Expressions.

I guess I was reading too fast. I missed or misread this last sentence
entirely:

> In particular, if the terminal
> >> transform returns an Expression, that Expression will
> >> get processed again.
>

I get what you're concerned about now. That's not an issue. An expression
is processed at most once by transform().

If, during a call to transform(), an expression E matches the
transform-object TO, the result of TO(E) is used in the output of
transform(). If, in the TO(E) call, its children are processed in some
way, it is the user's responsibility not to infinitely recurse.
transform() will copy or move the partial result TO(E), but will never
revisit/re-transform it.

[snip]

Unless I'm misunderstanding something, the following
> (seemingly reasonable) code will blow up the stack with
> infinite recursion:
>
> int check_int(int i)
> {
> assert(i >= -42 && i < 42);
> return i;
> }
>
> int checked_add(int i, int j)
> {
> return check_int(i + j);
> }
>
> struct checked_call
> {
> template<class F>
> int operator()(F f, int i)
> {
> return check_int(f(i));
> }
> };
>
> struct int_checker
> {
> auto operator()(terminal_tag, placeholder<I>)
> {
> return make_expression<expr_kind::call>(check_int,
> placeholder<I>{});
> }
> template<class L, class R>
> auto operator()(plus_tag, L && lhs, R && rhs)
> {
> return make_expression<expr_kind::call>(checked_add,
> transform(lhs, *this), transform(rhs, *this));
> }
> template<class F, class E>
> auto operator()(call_tag, F&& f, E&&... e)
> {
> return make_expression<expr_kind::call>(checked_call{},
> f, transform(e, *this)...);
> }
> };
>
> abs_ = make_terminal((int(*)(int))&std::abs);
>
> evaluate(transform(abs_(1_p + 2_p), int_checker{}), -5, 20);

 Let's look at the call graph for this:

First transform() will call operator()(call_tag, ...) on abs_(1_p + 2_p).
The result is make_expression<expr_kind::call>(checked_call{}, abs_,
transform(1_p + 2_p), *this).

To get that result, transform() will call (a *new* call to transform() at
the user's request, not as part of the top-level call): operator()(plus_tag,
...) on 1_p and 2_p. The result is make_expression<expr_kind::
call>(checked_add{}, transform(1_p, *this), transform(2_p, *this)).

Just following the left side: to evaluate that, transform() (again, a *new*
call): operator()(terminal_tag, 1_p), which returns
make_expression<expr_kind::call>(check_int, 1_p).

The right does something very similar, just with 2_p.

No infinite recursion.

Zach


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk