Boost logo

Boost :

From: Terje Slettebø (tslettebo_at_[hidden])
Date: 2002-09-19 02:26:48


>From: "Jaap Suter" <J.Suter_at_[hidden]>

> consider the following code:
>
> apply_if< some_condition,
> some_type_1,
> some_type_2
> >::type
>
> We do this because we don't want some_type_2 evaluated if some_condition
is
> true, or vice versa. Very handy and I need it a lot. However, consider the
> following case:
>
> apply_if< some_condition,
> some_type_1,
> plus< some_type_2::type, some_number >
> >::type
>
> In this case, I get the impression that my compiler (Intel) evaluates the
> template parameters of plus, and thus of some_type_2::type, even when it
is
> not used.

Yes, and it is right in doing so. When you apply "::type" to a type, it will
evaluate it, regardless of what branch of the apply_if that is taken.

> However, as in the mpl::apply_if rationale, this may not always
> work.
>
> Thus, I provide the following indirection:
>
> template< ... >
> struct recurse
> {
> typedef plus< some_type_2::type, some_number > type
> };
>
> apply_if< some_condition,
> some_type_1,
> recurse
> >::type
>
> And now it works, because the compiler no longer evaluates the
> some_type_2::type, if it is not needed.
>
> Now, I have the following questions:
>
> 1. Can you acknowledge this problem? The fact that the compiler evaluates
> template parameters for non-used types, and consequently that the
> mpl::apply_if
> rationale applies here as well?

I can certainly acknowledge that. In fact, I've suggested that all
metafunctions evaluate their own arguments. [Addition: I see you suggest the
same below here. :) ] Similar to how apply_if evaluates the selected
argument, as you say.

I found the exact same problem, too, when working on some metafunctions
during the MPL formal review.

In the posting with my "factorial" metafunction
(http://aspn.activestate.com/ASPN/Mail/Message/1316005), I propose to make
the metafunctions evaluate their own arguments (apply "::type" to them),
just like it happens in run-time functions. With that, the factorial example
becomes (quoting from my posting):

template<class V>
struct factorial
{
  typedef typename V::type Value;

  typedef typename apply_if<equal_to<Value,value<Value,0> >,
    value<Value,1>,
    mul<Value,factorial<prior<Value> > >
>::type type;
}

The first typedef evaluates the metafunction argument, V.

"value" is another new metafunction. It's a way to provide a generic
constant, of the type given in the first argument, and the value given in
the second. Thus, if "Value" is int_c<...>, then "value<Value,1>::type" is
int_c<1>.

Notice the recursive call to "factorial" in the else-part of apply_if. Had
it been written factorial<...>::type, the code above would have lead to
infinite recursion, as the recursive call would always be done.

To take your code example, again:

> apply_if< some_condition,
> some_type_1,
> plus< some_type_2::type, some_number >
> >::type

The problem that you mention is, as you say, because the "::type" of the
"some_type2::type" evaluates "some_type2," regardless of what branch of the
apply_if that is selected. Like in the example with factorial, above, if
it's a recursive metafunction call, it may lead to infinite recursion.

If the metafunctions, like plus, evaluated their own arguments, as
suggested, you could write it like this [Addition: I see that you've come to
this, yourself, too, later here]:

apply_if< some_condition,
               some_type_1,
               plus< some_type_2, some_number >
>::type

This avoids evaluating some_type_2, unless that part of the if-branch is
taken. Note that for this to work, all values must have a "type" member,
which is the case in MPL. If a type isn't a metafunction, "type" simply
provides the identity, the same type. So "::type" may be applied
unconditionally on all types.

I found that such a change of MPL would greatly simplify writing code for
it, as you avoid having to create ad-hoc templates (like your "recurse",
above), just to avoid premature instantiation (evaluation) of some_type_2.
Thus, you can write the code in a natural way, not having to spread it out.
I found this spreading out to be similar to how template specialisations
spreads code, which is what functions like apply_if is meant to avoid. You
also avoid all those "::type"s in the code.

Paul Mensonides replied at the time that he found this a sensible solution
to it. However, I haven't heard anything on this issue from others, such as
Aleksey or Dave.

> 2. Is this the best solution to solve this problem?

I think making the metafunctions evaluating their arguments is better.
However, that requires a fundamental change to MPL. To use MPL as it is,
your solution, which I came to, too, is more or less the only solution to
it. It does seem like return to specialisations in disguise, though, as
mentioned, with the code being spread out, only because of details of the
implementation.

> 3. Maybe we can introduce the indirection in plus and other meta-functions
> that act on integral constants directly. Allow me to explain:
>
> integral_c and the other (bool_c, int_c, etc.) all support the ::type
> construct that reflects upon itself. We can use this to allow plus (for
> example) to always query its arguments for the type.
>
> Current mpl::plus implementation:
>
> template< typename A, typename B >
> struct plus
> {
> typedef integral_c< A::value_type, A::value + B::value > type;
> }
>
> My proposed mpl::plus implementation:
>
> template< typename A, typename B >
> struct plus
> {
> typedef integral_c< A::type::value_type, A::type::value +
B::type::value
> > type;
> }
>
> This allows us to pass unfinished expressions into meta-functions. A sort
of
> lazy evaluation. Using this, my second code example could have been
written
> as:
>
> apply_if< some_condition,
> some_type_1,
> plus< some_type_2, some_number >
> >::type
>
> and would have worked.

Exactly.

> However, I agree that this is a bit ugly.

Not at all. If all metafunctions does this, it works like a charm. I've used
it.

If you think about it, it's the same way run-time functions work, as
mentioned. They evaluate their arguments before entering the function body,
too.

> For example, why limit this to
> only one -type depth? Why not call ::type on the incoming parameters until
> we reach a type that has a ::value parameter?

It's not necessary if all metafunctions does it.

> I don't know. Perhaps my
> suggested work-around using the recurse helper-meta-function is better.

I don't think so. However, as MPL is currently defined, it's more or less
the only way.

Regards,

Terje


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