Topic: Q: for_each() standard behaviour with function objects


Author: Joel Conraud <conraud@bergerac.ensg.u-nancy.fr>
Date: 1998/10/21
Raw View

        I try to replicate the example in section 18.4 of the third
edition of "the C++ programming language", by Bjarne Stroustrup. This
example is about the use of function objects with for_each() standard
library algorithm.

With the STL implementations I have access to (i.e. : VC++ 5.0 and HP
aCC), the example doesn't work.

Here is the example:

template <class T> class Sum {
    T res;
public:
    Sum(T i = 0) : res(i) {}
    void operator()(T x) { res += x; }
    T result() const { return res; }
};

void f(list<double>& ld) {
    Sum<double> s;
    for_each(ld.begin(), ld.end(), s);
    cout << "the sum is " << s.result() << '\n';
}

Whatever the 'ld' list is made of, 's.result()' is equal to 0. Why ? This
is because the signature of for_each() in the STL implementations I use:

template <class InputIterator, class Function>
Function for_each (InputIterator first, InputIterator last, Function f);

Here, the Function parameter is given by VALUE, not by reference. So
in the example above 's' is copied, summing is computed the right way
on the temporary copy of 's' and the result of the computation is lost
at the end of for_each()!

For sure, the example above would have worked with the following
modification:

replace: for_each(ld.begin(), ld.end(), s);
by: s = for_each(ld.begin(), ld.end(), s);

But this implies that copy constructors and assignment operators on
Function are called, and these calls can be expensive, when the
replacement of 'Function f' parameter in for_each() signature by
'Function& f' would have made work the example the right way, and the
most efficient way.

Am I missing something? Or is there some problem with the STL
implementations I'm working with?

        Thanks for your help,

--
Joel CONRAUD                                    mailto:conraud@t-surf.com
TSurf SA. The gOcad company.                    http://www.t-surf.com
rue du doyen Marcel Roubault                    Tel: +33 (3) 83 59 64 30
54500 Vandoeuvre les Nancy (France)             Fax: +33 (3) 83 59 64 33



[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: sbnaran@fermi.ceg.uiuc.edu (Siemel Naran)
Date: 1998/10/21
Raw View
On 21 Oct 1998 16:36:21 GMT, Joel Conraud

>        I try to replicate the example in section 18.4 of the third
>edition of "the C++ programming language", by Bjarne Stroustrup. This
>example is about the use of function objects with for_each() standard
>library algorithm.

This is a bug in Stroustrup.  Of course, since 's' is passed by value,
it is the copy that is modified, not the original.


>    Sum<double> s;
>    for_each(ld.begin(), ld.end(), s);
>    cout << "the sum is " << s.result() << '\n';


>For sure, the example above would have worked with the following
>modification:

[snip]

>But this implies that copy constructors and assignment operators on
>Function are called, and these calls can be expensive, when the
>replacement of 'Function f' parameter in for_each() signature by
>'Function& f' would have made work the example the right way, and the
>most efficient way.

Since the function for_each is (usually) inline and the object Sum
is inline (every one of its funcs is inline), there should be
absolutely no penalty for using your modification.  Most compilers
don't yet do such thorough optimizations, but just pretend that they
do because it leads to a cleaner style of programming.  In any case,
why not try this:

     cout << "the sum is "
          << for_each(ld.begin(), ld.end(), Sum<double>())
          << '\n';

Or better yet, use 'accumulate' instead of 'for_each'.



Why is it that in STL, function objects are passed by value, not
reference?  This question has occupied me for some time.  Here are
some possible answers.  Add your own reasons.

(1) If pass by const reference, function object can't be modified.
    This is a problem as some algorithms may want to modify the
    function object.  The above example is a case is point.

(2) If pass by non-const reference, then we can't pass temporaries
    as above (my modification); passing temporaries is actually very
    convenient.

(3) If pass by reference, we can take advantage of virtual func
    mechanism.  This requires a base class Function object with a
    virtual operator() function.
    [-] But this approach prevents inlining of quick funcs, like
        your Sum::operator() above.
    [-] There's also the annoying issue of whether Function::operator()
        should be const or not.  Our decision here constrains the
        signature of the operator() of the derived classes.
    [-] We wind up with complicated argument lists, as evidenced below.

    // if STL were like this, we could take advantage of virtual funcs

    template <class T1,T2>
    struct Function : public unary_function<T1,T2>
    {
         virtual result_type operator()(argument_type) const = 0;
    };

    template <class Iter, class T1, class T2>
    inline void for_each(Iter begin, Iter end, Function<T1,T2>& f);


    template <class T> class Sum : public Function<const T&,T> { ... };

    int main()
    {
         Sum<double> s;
         for_each(ld.begin(),ld.end(),s);
    }



--
----------------------------------
Siemel B. Naran (sbnaran@uiuc.edu)
----------------------------------


[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: brownsta@concentric.net (Stan Brown)
Date: 1998/10/22
Raw View
sbnaran@fermi.ceg.uiuc.edu (Siemel Naran) wrote:
>On 21 Oct 1998 16:36:21 GMT, Joel Conraud
>>        I try to replicate the example in section 18.4 of the third
>>edition of "the C++ programming language", by Bjarne Stroustrup. This
>>example is about the use of function objects with for_each() standard
>>library algorithm.
>
>This is a bug in Stroustrup.  Of course, since 's' is passed by value,
>it is the copy that is modified, not the original.

For what it's worth, the bug has already been corrected. His errata sheet
corrects the code in question to

    Sum<double> s;
    s = for_each(ld.begin(), ld.end(), s);
    cout << "the sum is " << s.result() << '\n';

That is, it assigns the return value of for_each() to s.

The errata for /The C++ Programming Language, Third Edition/ are at
 http://www.research.att.com/~bs/3rd_errata.html
Look before you print: you will consume many sheets of paper.

--
Stan Brown, Oak Road Systems, Cleveland, Ohio, USA
                      http://www.concentric.net/%7eBrownsta/
My reply address is correct as is. The courtesy of providing a correct
reply address is more important to me than time spent deleting spam.


[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: zalman@netcom.com (Zalman Stern)
Date: 1998/10/23
Raw View
I too was surprised by the function object argument passing conventions in
STL. In particular with a piece of code like so:

 class Delayet {
  Delayet() {  /* setup */ };
  operator() (T &chto) { /* operate */ };
  ~Delayet() {  /* cleanup */ };
 };

 vector<Korovki> vcegda();

 for_each(vcegda.begin(), vcegda.end(), Delayet());

Where the /* setup */, /* operate */, and /* cleanup */ blocks comprise a
sort of extended function object protocol for loops. But it doesn't work
very well as the constructor and destructor get called more than once since
for_each returns the function object. I thought about forcing pass by
reference, but it starts to feel wrong pretty quickly. (One can also finese
the issue by putting in a copy constructor and making the destructor a
little more complex. That doesn't feel very satisfying either.)

-Z-


[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]