Topic: Perfect Forwarding vs. Pass By Value


Author: "Paul Mensonides" <pmenso57@comcast.net>
Date: Mon, 19 Apr 2010 11:36:33 CST
Raw View
In template code, I'm finding myself jumping through hoops to deal with
types that should be passed by value versus types passed by way of "perfect
forwarding".

For example, I'm ending up with code that looks like this:

#include <type_traits>
#include <utility>

template<class T> class A {
   public:
       template<class U> A(
           U v,
           typename std::enable_if<
               std::is_scalar<U>::value
           >::type* = 0
       ) : v_(v) { }
       template<class U> A(
           U&& v,
           typename std::enable_if<
               !std::is_scalar<typename
std::remove_reference<U>::type>::value
               && !std::is_array<typename
std::remove_reference<U>::type>::value
               && !std::is_base_of<
                   A,
                   typename std::remove_reference<U>::type
               >::value // disable use as copy constructor
           >::type* = 0
       ) : v_(std::forward<U>(v)) { }
   private:
       T v_;
};

That's an awful lot of clutter to pass scalar types by value as opposed to
some form of reference.  Naturally, a lot of the above can be abstracted
into reusable components:

#include <type_traits>
#include <utility>

template<class T> struct scalar_only
   : std::enable_if<std::is_scalar<T>::value> { };

template<class T, class U = void> struct non_scalar_only
   : std::enable_if<
       #define _ typename std::remove_reference<T>::type
       !std::is_scalar<_>::value
       && !std::is_array<_>::value
       && !std::is_base_of<U, _>::value
       #undef _
   > { };

template<class T> class A {
   public:
       template<class U> A(U v, typename scalar_only<U>::type* = 0)
           : v_(v) { }
       template<class U> A(U&& v, typename non_scalar_only<U, A>::type* =
0)
           : v_(std::forward<U>(v)) { }
   private:
       T v_;
};


This, of course, instantly turns combinatorial when there is more than
argument.  Of course, I can generate the combinatorial explosion with the
preprocessor, but that's awfully heavy-handed.

1) Am I misunderstanding the way that "perfect forwarding" is supposed to
work?

2) If not, is the only solution to avoid the combinatorial explosion to just
use references?

It would be nice if there was a way to "compute" the declared type given the
type of the actual argument _prior_ to substitution.  E.g.

template<class T> struct compute
   : std::conditional<
       std::is_scalar<T>::value || std::is_array<T>::value,
       T, T&&
   > { };

template<class T = compute<#1>::type, class U = compute<#2>::type> void f(T
a, U b) { }

Regards,
Paul Mensonides



--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use
mailto:std-c++@netlab.cs.rpi.edu<std-c%2B%2B@netlab.cs.rpi.edu>
]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Mathias Gaunard <loufoque@gmail.com>
Date: Wed, 21 Apr 2010 00:22:49 CST
Raw View
On 19 avr, 18:36, "Paul Mensonides" <pmens...@comcast.net> wrote:
> In template code, I'm finding myself jumping through hoops to deal with
> types that should be passed by value versus types passed by way of "perfect
> forwarding".

What's the point of that?
Your forwarding function should be inlined anyway.

--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Anthony Williams <anthony.ajw@gmail.com>
Date: Wed, 21 Apr 2010 00:22:20 CST
Raw View
"Paul Mensonides" <pmenso57@comcast.net> writes:

> In template code, I'm finding myself jumping through hoops to deal with
> types that should be passed by value versus types passed by way of "perfect
> forwarding".

Why? "Perfect forwarding" works for all types, including scalars.

Anthony
--
Author of C++ Concurrency in Action     http://www.stdthread.co.uk/book/
just::thread C++0x thread library             http://www.stdthread.co.uk
Just Software Solutions Ltd       http://www.justsoftwaresolutions.co.uk
15 Carrallack Mews, St Just, Cornwall, TR19 7UL, UK. Company No. 5478976

[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: "Paul Mensonides" <pmenso57@comcast.net>
Date: Wed, 21 Apr 2010 12:23:09 CST
Raw View
"Anthony Williams" <anthony.ajw@gmail.com> wrote in message
news:87zl0y33ce.fsf@dell.justsoftwaresolutions.co.uk...
> "Paul Mensonides" <pmenso57@comcast.net> writes:
>
>> In template code, I'm finding myself jumping through hoops to deal with
>> types that should be passed by value versus types passed by way of
>> "perfect
>> forwarding".
>
> Why? "Perfect forwarding" works for all types, including scalars.

My actual code doesn't just pass scalars that way.  Given some of the
unimplementable-without-compiler-support type traits that are in the
standard library now, it is possible to make much more informed decisions in
regards to passing by value versus passing by reference in template code
than it used to be, so it is more like "copy is bitwise and less than some
fixed size".  Also, some forwarding is not in an inlineable context--which
would imply passing around int values (not "objects") by reference and
accessing through indirection and the inverse optimization that that implies
both because of the inefficiency of indirection but because the potential
for aliasing comes into play.  (I realize that this not a whole lot
different than generic code in C++98.)

At this point, I'm sort of feeling out the mechanism.  However, AFAICT,
"perfect forwarding" makes it impossible for me to provide const correctness
as a contract without bubbling implementation detail or making the const
correctness of the interface dependent on changes (or additions to) to the
interface being forwarded to.  At my current level of understanding of the
feature, particularly in the context of a template type deduction with T&&,
it seems that you either have const correctness or perfect forwarding, but
not both unless you throw SFINAE at it all over the place.  These two
declarations are _hugely_ different in semantics, and the third one doesn't
allow moves.

   void f(X&&);                   // move only
   template<class T> void f(T&&); // anything (i.e. zero guarantees)

   template<class T> void f(const T&&); // no move?

It may well be that I'm not seeing the whole picture.  As I said, I'm just
feeling it out at this point, but I'm not yet comfortable with the possible
implications.

Regards,
Paul Mensonides



--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use
mailto:std-c++@netlab.cs.rpi.edu<std-c%2B%2B@netlab.cs.rpi.edu>
]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: "Paul Mensonides" <pmenso57@comcast.net>
Date: Wed, 21 Apr 2010 12:21:32 CST
Raw View
"Mathias Gaunard" <loufoque@gmail.com> wrote in message
news:720041d0-ae8d-483a-9327-5386a886a346@c1g2000vbc.googlegroups.com...
> On 19 avr, 18:36, "Paul Mensonides" <pmens...@comcast.net> wrote:
>> In template code, I'm finding myself jumping through hoops to deal with
>> types that should be passed by value versus types passed by way of
>> "perfect
>> forwarding".
>
> What's the point of that?
> Your forwarding function should be inlined anyway.

Not if it doesn't just forward arguments.  E.g.

class X {
 public:
   template<class T> X(T&& s);
 private:
   std::string s_;
};

template<class T> X::X(T&& s) : s_(std::forward<T>(s)) {
 // do a bunch of other stuff...
}

Regards,
Paul Mensonides



--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use
mailto:std-c++@netlab.cs.rpi.edu<std-c%2B%2B@netlab.cs.rpi.edu>
]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Howard Hinnant <howard.hinnant@gmail.com>
Date: Wed, 21 Apr 2010 19:18:57 CST
Raw View
On Apr 21, 2:23 pm, "Paul Mensonides" <pmens...@comcast.net> wrote:

> At my current level of understanding of the
> feature, particularly in the context of a template type deduction with
T&&,
> it seems that you either have const correctness or perfect forwarding, but
> not both unless you throw SFINAE at it all over the place.  These two
> declarations are _hugely_ different in semantics, and the third one
doesn't
> allow moves.
>
>    void f(X&&);                   // move only
>    template<class T> void f(T&&); // anything (i.e. zero guarantees)
>
>    template<class T> void f(const T&&); // no move?

Passing a const expression (say const X) to your f(T&&) will cause T
to be deduced as const X, if the expression passed is an rvalue, and
const X& if the expression is an lvalue.  I.e. the cv *and* the lvalue/
rvalue properties are both encoded into the type of T.

-Howard


--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use
mailto:std-c++@netlab.cs.rpi.edu<std-c%2B%2B@netlab.cs.rpi.edu>
]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: "Paul Mensonides" <pmenso57@comcast.net>
Date: Fri, 23 Apr 2010 11:01:31 CST
Raw View
"Howard Hinnant" <howard.hinnant@gmail.com> wrote in message
news:96473530-ed33-42c0-ab94-f7b5da680a31@k15g2000vbd.googlegroups.com...
> On Apr 21, 2:23 pm, "Paul Mensonides" <pmens...@comcast.net> wrote:

>>    void f(X&&);                   // move only
>>    template<class T> void f(T&&); // anything (i.e. zero guarantees)
>>
>>    template<class T> void f(const T&&); // no move?
>
> Passing a const expression (say const X) to your f(T&&) will cause T
> to be deduced as const X, if the expression passed is an rvalue, and
> const X& if the expression is an lvalue.  I.e. the cv *and* the lvalue/
> rvalue properties are both encoded into the type of T.

Right, so if I want to have a 'f' provide the guarantee that it doesn't
modify its argument (except to move from an rvalue), I have to use something
other than std::forward.  Something like:

#include <iostream>
#include <type_traits>
#include <utility>

// safe_forward...

#define R \
   typename std::conditional< \
       std::is_lvalue_reference<T>::value \
       && !std::is_const<typename std::remove_reference<T>::type>::value, \
       const typename std::remove_reference<T>::type&, T&& \
   >::type \
   /**/

template<class T, class U>
typename std::enable_if<
   std::is_convertible<
       typename std::remove_reference<U>::type*,
       typename std::remove_reference<T>::type*
   >::value
   && (!std::is_lvalue_reference<T>::value ||
std::is_lvalue_reference<U>::value),
   R
>::type safe_forward(U&& u) {
   return static_cast<R>(u);
}

#undef R

// test...

template<class T> void f(T&& x) {
   std::cout
       << std::is_const<typename std::remove_reference<T&&>::type>::value
       << '\n';
}

template<class T> void g(T&& x) {
   f(safe_forward<T>(x));
}

struct C { };

int main() {
   C x;
   const C y = C();
   g(x);               // forwards as: const C&
   g(y);               // forwards as: const C&
   g(C());             // forwards as: C&&
   g(static_cast<const C>(C()));
                       // forwards as: const C&&
   return 0;
}



--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use
mailto:std-c++@netlab.cs.rpi.edu<std-c%2B%2B@netlab.cs.rpi.edu>
]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Howard Hinnant <howard.hinnant@gmail.com>
Date: Mon, 26 Apr 2010 00:23:07 CST
Raw View
On Apr 23, 1:01 pm, "Paul Mensonides" <pmens...@comcast.net> wrote:
> "Howard Hinnant" <howard.hinn...@gmail.com> wrote in message
>
> > Passing a const expression (say const X) to your f(T&&) will cause T
> > to be deduced as const X, if the expression passed is an rvalue, and
> > const X& if the expression is an lvalue.  I.e. the cv *and* the lvalue/
> > rvalue properties are both encoded into the type of T.
>
> Right, so if I want to have a 'f' provide the guarantee that it doesn't
> modify its argument (except to move from an rvalue), I have to use
something
> other than std::forward.  Something like:
>
> // safe_forward...
>
> #define R \
>    typename std::conditional< \
>        std::is_lvalue_reference<T>::value \
>        && !std::is_const<typename std::remove_reference<T>::type>::value,
\
>        const typename std::remove_reference<T>::type&, T&& \
>    >::type \
>    /**/
>
> template<class T, class U>
> typename std::enable_if<
>    std::is_convertible<
>        typename std::remove_reference<U>::type*,
>        typename std::remove_reference<T>::type*
>    >::value
>    && (!std::is_lvalue_reference<T>::value ||
> std::is_lvalue_reference<U>::value),
>    R>::type safe_forward(U&& u) {
>
>    return static_cast<R>(u);
>
> }
>
> #undef R

Restating the goal of your safe_forward:  You want to forward except
adding const to lvalues.  There's nothing wrong with that design
goal.  Here's another implementation of the same thing:

template <class T>
struct add_const_to_lvalue
{
   typedef T type;
};

template <class T>
struct add_const_to_lvalue<T&>
{
   typedef const T& type;
};

template <class T, class U>
typename add_const_to_lvalue<T>::type&&
make_lvalues_const(U&& u)
{
   return std::forward<T>(u);
}

I wouldn't call this "perfect forwarding" as it does transform non-
const lvalues.  That being said, if that's what you want to do, it
looks very doable to me.  And you can do it without having to reinvent
std::forward.  Instead you can build on it.  If you would prefer not
to build on std::forward, you could use static_cast<T&&>(u) instead.
But then you loose some of the safety benefits mentioned in N2951.
<shrug> As author of this code you can make whatever tradeoffs make
the most sense for your application.

-Howard


--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use
mailto:std-c++@netlab.cs.rpi.edu<std-c%2B%2B@netlab.cs.rpi.edu>
]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: "Paul Mensonides" <pmenso57@comcast.net>
Date: Mon, 26 Apr 2010 22:59:43 CST
Raw View
"Howard Hinnant" <howard.hinnant@gmail.com> wrote in message
news:5b66d9e6-b6e8-42ff-8f29-40b955c5c659@v8g2000vbh.googlegroups.com...
> On Apr 23, 1:01 pm, "Paul Mensonides" <pmens...@comcast.net> wrote:

> Restating the goal of your safe_forward:  You want to forward except
> adding const to lvalues.  There's nothing wrong with that design
> goal.  Here's another implementation of the same thing:

That's exactly right.  The idea is to forward lvalue references to non-const
as lvalue references to const in order to have some hope of maintaining
const-correctness with pervasive use of move semantics in template code.

> template <class T>
> struct add_const_to_lvalue
> {
>   typedef T type;
> };
>
> template <class T>
> struct add_const_to_lvalue<T&>
> {
>   typedef const T& type;
> };
>
> template <class T, class U>
> typename add_const_to_lvalue<T>::type&&
> make_lvalues_const(U&& u)
> {
>   return std::forward<T>(u);
> }
>
> I wouldn't call this "perfect forwarding" as it does transform non-
> const lvalues.  That being said, if that's what you want to do, it
> looks very doable to me.  And you can do it without having to reinvent
> std::forward.  Instead you can build on it.  If you would prefer not
> to build on std::forward, you could use static_cast<T&&>(u) instead.
> But then you loose some of the safety benefits mentioned in N2951.
> <shrug> As author of this code you can make whatever tradeoffs make
> the most sense for your application.

That works perfectly.  With GCC demangling added for testing...

#include <cstdlib>
#include <iostream>
#include <string>
#include <memory>
#include <typeinfo>
#include <utility>

#include <cxxabi.h>

// impl...

namespace detail {

   template<class T> struct forward_ {
       typedef T type;
   };

   template<class T> struct forward_<T&> {
       typedef const T& type;
   };

}

template<class T, class U>
inline typename detail::forward_<T>::type&& forward(U&& u) {
   return std::forward<T>(u);
}

// test...

// IMO: It is a shame that the highly-useful
//      'identity' meta-function got removed from
//      the standard library.

template<class T> struct identity {
   typedef T type;
};

std::string demangle(const char* id) {
   int status;

   // IMO: A type equivalent to this should be
   //      a standard library component.
   // (Function object used rather than pointer-to-function
   // to avoid non-portable stuff like calling-convention.)

   struct deleter {
       inline void operator()(void* p) {
           std::free(p);
       }
   };
   std::unique_ptr<char, deleter>
       p(abi::__cxa_demangle(id, 0, 0, &status));
   return p ? p.get() : "?";
}

template<class T> void f(T&& x) {
   std::cout << demangle(typeid(identity<T&&>).name()) << '\n';
}

template<class T> void g(T&& x) {
   f(forward<T>(x));
}

struct C { };

int main() {
   C x;
   const C y = C();
   g(x);   // outputs: identity<C const&>
   g(y);   // outputs: identity<C const&>
   g(C()); // outputs: identity<C&&>
   g(static_cast<const C>(C()));
           // outputs: identity<C const&&>
   return 0;
}

The only difference between 'std::forward' and this 'forward' (besides the
reference to non-const lvalue stuff) is that this 'forward' doesn't remove
the function from the overload set according the criteria specified in the
standard.  Which, in this particular case, is probably not the end of the
world because 'std::forward' is still used (and therefore, a diagnostic
would still be generated one way or another).

Other than in the implementation of 'forward' above, I will pretty much
never directly use 'std::forward'.  I consider it to be fundamentally unsafe
WRT const-correctness.  Yes, the deduction of 'T&&' is similar to 'T&' (WRT
cv-qualifiers), but in the "olden days", there was no point to using 'T&'
(as opposed to 'const T&') unless the value was potentially modified.  With
'T&&', to provide forwarding while enabling rvalue moves you must keep the
door wide open.  I.e. 'const T&&' appears to be useless.  That is, unless
you use hijinks like the above.  Otherwise, you get all of the benefits of
rvalue moves at the cost of throwing any reasoning WRT const-correctness out
the window.

Thanks for the better implementation, BTW.  I'm still wrapping my head
around the reference-collapsing and deduction rules.  This pretty much
solidifies how I need to use forwarding.

Now I just need to decide how (or, rather, whether) to handle the
combinatorial scenarios generated by pass-by-value vs.
pass-by-some-type-of-reference.  Which, IMO, may be necessary to handle in
non-inlined scenarios.  My current predicate for whether a type should be
passed by value looks like this:

namespace detail {

   template<class T> struct is_byval_
       : std::integral_constant<
           bool,
           std::is_array<T>::value
           || std::is_function<T>::value
           || std::is_scalar<T>::value
           || std::is_trivially_copyable<T>::value
              && !std::is_polymorphic<T>::value
              && sizeof(T) <= 16
              // where '16' is a magic number
              // to be based on profiling
       > { };

}

template<class T> struct is_byval
   : detail::is_byval_<
       typename std::remove_const<
           typename std::remove_reference<T>::type
       >::type
   > { };

For example, I have a complex number class that allows infinities.  I
represent a directed infinity by (x + iy)inf and complex infinity by (0 +
0i)inf.  (Indeterminate values such as an actual 0 * inf are handled
separately.)  However, unlike std::complex, the scalar type need not be a
C++ scalar type.  It could be an arbitrary precision integer for Gaussian
numbers, some type of arbitrary precision rational value, or even some type
involving symbolics or expression trees for reals.  I have a constructor
that looks like this:

complex(T re = T(0), T im = T(0), bool inf = false);

Now, if 'inf' is true, I need to reduce the real and imaginary parts so that
they represent the phase angle of the complex number in lowest terms.  I.e.
complex(2, 2, true) is the same directed infinity as complex(1, 1, true) and
should compare equal.  IOW, the function is not likely to be inlined unless
I move the reduction to another (non-inlined) member function (which would
probably be better anyway, but I'm trying to come up with an example).

template<class T> class complex {
   public:
       typedef T value_type;
       template<class U, class V>
       complex(U&& re = T(0), V&& im = T(0), bool inf = false)
           : re_(forward<U>(re)), im_(forward<V>(im)), inf_(inf) {
           if (inf_ && re_ + im_ != T(0)) {
              // reduce...
           }
       }
   private:
       T re_, im_;
       bool inf_;
};

What if 'T' is something simple like 'long double'?  Every reference to
arguments implies an indirection and the lack of optimizations (due to
aliasing/threading) that that, in turn, implies.  Within a constructor
itself, aliasing to the members of 'this' can only occur down the call
hierarchy, and the compiler could be smart (to a degree) in how it handles
it.  With the arguments, OTOH, aliasing could occur from up the call
hierarchy.  I.e. 're' could refer to the same object as 'im' (which would
certainly be dangerous in a move scenario).

I could of course, provide 4 overloads of every possible combination or
forwarded input types (i.e. is_byval vs. !is_byval), or is it better to just
pass by value and move?

complex(T re = T(0), T im = T(0), bool inf = false)
   : re_(std::move(re)), im_(std::move(im)), inf_(inf) {
   // etc...
}

I.e. instead of constructing the members with the input values at a "deep"
point, construct them as temporaries immediately and rely on move.  If that
is better, is there any point to "perfect forwarding" (or "almost perfect
forwarding") in a context where a value is constructed from the forwarded
value (other than the bazillions of heavy types out there that don't
implemented move)?

This certainly seems simpler from an implementation standpoint in the "with
intent to copy" case.  In the "without intent to copy" case or the "unknown
intent" case of pure forwarding, it seems you still have to fall back to
either passing everything by reference or combinatorial overloading with
SFINAE.  E.g.

// no forward because multi-move = bad news...

template<class T, class U> void f(const T& x, const U& y) {
   for (int i = 0; i != 100; ++i) {
       g(x, y);
   }
}

// vs.

template<class T, class U>
typename std::enable_if<
   is_byval<T>::value
   && is_byval<U>::value
>::type
f(T x, U y) {
   for (int i = 0; i != 100; ++i) {
       g(x, y);
   }
}

template<class T, class U>
typename std::enable_if
   is_byval<T>::value
   && !is_byval<U>::value
>::type
f(T x, const U& y) {
   for (int i = 0; i != 100; ++i) {
       g(x, y);
   }
}

template<class T, class U>
typename std::enable_if
   !is_byval<T>::value
   && is_byval<U>::value
>::type
f(const T& x, U y) {
   for (int i = 0; i != 100; ++i) {
       g(x, y);
   }
}

template<class T, class U>
typename std::enable_if
   !is_byval<T>::value
   && !is_byval<U>::value
>::type
f(const T& x, const U& y) {
   for (int i = 0; i != 100; ++i) {
       g(x, y);
   }
}

The latter combinatorial case is painful (obviously), but it is more
efficient.  It is also actually possible to do effectively in generic code
given the type traits required by C++0x (as opposed to C++98).  The degree
of efficiency gain may be marginal, but that ultimately depends on how many
times a small easy-to-copy value (such as an int) is referenced in the
ultimately-forwarded-to function or how many times the ultimately-forward-to
function is called and generated code locality.  There are apparently two
less-than-ideal choices.  The question that I have is which of them is
worse.  Note that, for me, the combinatorial case is both viable and
maintainable since I would never actually directly write them.  I'd generate
them and generalize the generator.

A lot of problems would be solved if there was a way to provide
user-specified type deduction in lieu of built-in type deduction:

template<
   class T
   = typename std::conditional<
       is_byval<#1>::value,
       typename std::remove_reference<#1>::type,
       const typename std::remove_reference<#1>::type&
   >::type
>
void f(typename identity<T>::type x) {
   for (int i = 0; i != 100; ++i) {
       g(x);
   }
}

Where #1 refers to the decltype of the actual argument (which the compiler
knows at the point of the call).  So, for

f(123);

the resulting overload that gets added to the overload set would be

void f(int)

for

f(std::string("abc"));

the resulting overload that gets added to the overload set would be

void f(const std::string&);

With a simpler forwarding scenario (i.e. not multi-forwarded) like 'f', a
different deduction that allows moving would be used.

Regards,
Paul Mensonides



--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]