
Scott Meyers <usenet <at> aristeia.com> writes:
Joaquín Mª López Muñoz wrote:
[...]
The problem now is that, although the eraseVal<...> expression is only invoked (i.e. its nested ::type calculated) when strictly necessary --this is what eval_if is used for--, its first argument is computed *always*, regardless of what branch of the eval_if<...> expr is finally selected:
Is this sort of like saying that although a metafunction may not be invoked, it's arguments are always calculated?
No, not exactly. The thing to watch for is the rules for implicit instantation of class templates in C++, which roughly boil down to "a mention of a class template specialization will implicit instantiate it only if necessary". In particular, the mere mention of a class template specialization X<...> does not cause instantiation, but the expression X<...>::Y causes the implicit instantiation of X<...> (although it does not cause the instantiation of Y in case Y is also a template specialization.) This applies recursively to arguments of X. So, if you consider again the eval_if expression mpl::eval_if< boost::is_same< iter, typename mpl::end<Seq>::type >, mpl::identity<Seq>, eraseVal< typename mpl::erase<Seq,iter>::type, typename mpl::next<iter>::type >
the mere mention of this expression causes the implicit instantation of the following (and only the following): mpl::end<Seq> mpl::erase<Seq,iter> mpl::next<iter> So the problem does not actually have much to do with metafunctions, we're talking pure C++ templates here.
I'm probably still stuck in the procedural world, but my expectation was that the false branch would not be "taken" when the condition was false, but it's still murky to me what it means to "take" a branch, because where "taking" it entails some kind of template expansion along it. At least that's what I currently think.
Of course, the instantation of eval_if<...> (when you add the ::type suffix) causes the instantiation of more types, namely those in the true or false branch depending on the condition, just as expected. But the problem lies in the implicit instantiations caused by C++ rules before eval_if is even invoked.
So, what we need if to add a layer of indirection so as to not invoke the arguments to the recursive call to eraseVal except when strictly necessary, like for instance as follows:
template<typename Seq,typename T> struct eraseVal { template<typename Iter> struct eraseValRecurse: eraseVal<typename mpl::erase<Seq,Iter>::type,T> {};
typedef typename mpl::find<Seq,T>::type iter;
typedef typename mpl::eval_if< boost::is_same<iter,typename mpl::end<Seq>::type>, mpl::identity<Seq>, eraseValRecurse<iter>
And this works because we evaluate only iter in this last statement, not eraseValRecurse<iter> -- is that correct?
This works because the mere mention of eraseValRecurse<iter> does not cause any implicit instantiation --it is only when the "false" branch is taken that the expression is instantatiated causing the instantiation in turn of mpl::erase<...>: but in this context this latter instantiation is correct and does not lead to infinite recursions or invalid uses of MPL end iterators. Joaquín M López Muñoz Telefónica, Investigación y Desarrollo