Topic: Why is for_each operator parameter passed by value?
Author: Matt Austern <austern@isolde.mti.sgi.com>
Date: 1997/12/11 Raw View
inpact3 <inpact3@clr34el.der.edf.fr> writes:
> Maybe I missed something, but I can't see why operator()() isn't a
> const member by requirement. In math, when you apply a function f
> to an argument, that does not change the definition of function f !
>
> IHMO, I can see only two reasons for making operator() non-const :
> - trying to achieve side-effects, which I consider bad programming style,
> - updating implementation members of the functionoid ; the keyword mutable
> has been introduced for that need.
>
> Any examples of functionoids for which a non-const operator() is really
> needed are welcome...
Sure, here are a couple: a function object that performs I/O and
maintains a status word saying how many of the operations succeeded,
and a random number generator function object. (Note that one STL
algorithm, random_shuffle, actually uses such an object.)
The mathematical definition of "function" bears very little
resemblance to functions in programming language.
---
[ 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 ]
[ FAQ: http://reality.sgi.com/employees/austern_mti/std-c++/faq.html ]
[ Policy: http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu ]
Author: Valentin Bonnard <bonnardv@pratique.fr>
Date: 1997/12/11 Raw View
Oleg Zabluda <zabluda@math.psu.edu> writes:
> Valentin Bonnard <bonnardv@pratique.fr> wrote:
> : Oleg Zabluda <zabluda@math.psu.edu> writes:
>
> : > Here is one example:
> : >
> : > #include <iostream>
> : > #include <algorithm>
> : >
> : > using namespace std;
> : >
> : > class print{
> : > private:
> : > ostream& os_;
> : > public:
> : > print(ostream& os) : os_(os) {}
> : > void operator()(int i) /* non-const*/ { os_ << i; }
> : > };
> : >
> : > int main()
> : > {
> : > int a[3] = {1,2,3};
> : > for_each(a, a+3, print(cout));
> : > return 0;
> : > }
> : >
>
> : Nothing prevents operator() from being const here, except
> : if you change the os_ member to an ostream.
>
> You mean to "mutable ostream"?
I wrote the contrary (or did I ? perhaps it wasn't
very clear).
I mean that, _except_ if you want to have an ostream
member (but you have proved that it's silly), it will
work. You can declare the above operator() const:
ostream& os_;
void operator()(int i) const { os_ << i; }
It's ok.
> : An using a
> : cache is normally expressed with mutable.
>
> Maybe, but this is a design decision. Cache change might be considered
> to be a change in the logical state of an object. Say, if new values
> push out old ones, after your "const" method, your functionoid
> performance for some arguments might suffer, which might be considered
> to be an "observable event", and a change in the logical constness.
Speed is rarely considered an "observable event" (except
perhaps if you fail to meet your complexity garanties).
> : And of course you should use ostream_iterator, but you
> : certainly already know that, and it isn't the point.
>
> Speaking of which. ostream_iterator<> is one more example of a class
> for which storing a "mutable" copy, then mutating it (applying non-const
> methods to it) within a const method, makes no logical sense whatsoever.
You use ostream_iterator<> with copy anyway, not for_each.
I only meant that this should be written
copy (a, a+3, ostream_iterator<int> (cout));
But this is comment, not a counter-argument against you.
--
Valentin Bonnard mailto:bonnardv@pratique.fr
info about C++/a propos du C++: http://www.pratique.fr/~bonnardv/
---
[ 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 ]
[ FAQ: http://reality.sgi.com/employees/austern_mti/std-c++/faq.html ]
[ Policy: http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu ]
Author: Bill Rubin <rubin@contractor.net>
Date: 1997/12/07 Raw View
It was an unpleasant surprise for me to discover that the function
object (operator) parameter Op of for_each, transform, and
perhaps other STL functions, is passed by value, rather than
by reference. I "fixed this problem" for for_each by defining the
following pass-by-reference "extension":
template<class In, class Op> void for_each_ref(In first, In last, Op& f)
{/* Same definition as for_each, but without "return" statement */}
This extension, which differs from for_each only in the introduction of
"&" before f, and the elimination of the return value, overcomes all the
following limitations of for_each:
1. Invocation performance: Execution of Op copy ctor and dtor for
parameter f.
2. Return performance: Execution of Op copy ctor and dtor for return
statement.
3. Assignment: If the client of for_each wishes to use possible changes
of state of f caused by the invocation, she typically will assign the
result of for_each to the original argument. Not only is this an
additional performance hit, but one can easily forget to do it, thus
giving the wrong answer.
4. Object slicing: If for_each is invoked using a reference to an Op,
which reference actually points to an object of a subclass of Op,
the wrong behavior will be invoked.
5. Copyable objects: Only Op classes which are copyable by clients
can be used. A class Op fails this condition if its copy ctor is
declared private or protected, if its copy ctor is declared public
but not defined, or if its copy ctor is not declared *and* one or
more data members or base classes of Op is non-copyable.
6. Inappropriate copying: Even if Op *can* be copied, it may not make
logical sense to copy it within for_each invocations. Such copying
may have undesirable side effects.
7. Assignable objects: In the case of Item 2, Op generally must be
assignable. Op fails this condition for reasons corresponding to
those given in Item 4.
8. Inappropriate assigning: Analogous to Item 7.
Since for_each_ref has no return value, it removes limitations 2, 3, 7,
and 8. It removes the remaining limitations simply by passing Op by
reference.
"Prefer to pass objects by reference" is a well-known C++ coding
guideline. I'd be most interested to know what considerations
outweigh this guideline and the above items in the case of the
operator parameter of for_each (and transform, and perhaps others).
In other words, why wasn't for_each defined the way I've defined
for_each_ref?
--
William B. Rubin, Ph.D.
President, Quality Object Software, Inc.
18 Eagle Lane, Poughkeepsie, NY 12601
Phone: 914-471-7448
---
[ 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 ]
[ FAQ: http://reality.sgi.com/employees/austern_mti/std-c++/faq.html ]
[ Policy: http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu ]
Author: Oleg Zabluda <zabluda@math.psu.edu>
Date: 1997/12/09 Raw View
Bill Rubin <rubin@contractor.net> wrote:
: It was an unpleasant surprise for me to discover that the function
: object (operator) parameter Op of for_each, transform, and
: perhaps other STL functions, is passed by value, rather than
: by reference. I "fixed this problem" for for_each by defining the
: following pass-by-reference "extension":
: template<class In, class Op> void for_each_ref(In first, In last, Op& f)
: {/* Same definition as for_each, but without "return" statement */}
: This extension, which differs from for_each only in the introduction of
: "&" before f, and the elimination of the return value, overcomes all the
: following limitations of for_each:
[snip]
Matt Austern explained copule of times that if you pass f by non-const
reference, then f can't be a const object, and if you pass it by
const reference, you can't use f for which operator()() is not
a const member. Passing by value is the only choice.
Since you know that it is passed by value, it's your responsibility
to make sure it's legal for your f and that the overhead is reasonable.
It's true for all functionoids in the standard library.
Oleg.
--
Life is a sexually transmitted, 100% lethal disease.
---
[ 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 ]
[ FAQ: http://reality.sgi.com/employees/austern_mti/std-c++/faq.html ]
[ Policy: http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu ]
Author: inpact3 <inpact3@clr34el.der.edf.fr>
Date: 1997/12/10 Raw View
Oleg Zabluda wrote:
>
> Bill Rubin <rubin@contractor.net> wrote:
> : It was an unpleasant surprise for me to discover that the function
> : object (operator) parameter Op of for_each, transform, and
> : perhaps other STL functions, is passed by value, rather than
> Matt Austern explained copule of times that if you pass f by non-const
> reference, then f can't be a const object, and if you pass it by
> const reference, you can't use f for which operator()() is not
> a const member. Passing by value is the only choice.
>
Maybe I missed something, but I can't see why operator()() isn't a
const member by requirement. In math, when you apply a function f
to an argument, that does not change the definition of function f !
IHMO, I can see only two reasons for making operator() non-const :
- trying to achieve side-effects, which I consider bad programming style,
- updating implementation members of the functionoid ; the keyword mutable
has been introduced for that need.
Any examples of functionoids for which a non-const operator() is really
needed are welcome...
Jerome CHAROUSSET
---
[ 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 ]
[ FAQ: http://reality.sgi.com/employees/austern_mti/std-c++/faq.html ]
[ Policy: http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu ]
Author: Valentin Bonnard <bonnardv@pratique.fr>
Date: 1997/12/10 Raw View
Bill Rubin wrote:
>
> It was an unpleasant surprise for me to discover that the function
> object (operator) parameter Op of for_each, transform, and
> perhaps other STL functions, is passed by value, rather than
> by reference.
It's a general rule.
> I "fixed this problem" for for_each by defining the
> following pass-by-reference "extension":
>
> template<class In, class Op> void for_each_ref(In first, In last, Op& f)
>
> {/* Same definition as for_each, but without "return" statement */}
>
> This extension, which differs from for_each only in the introduction of
> "&" before f, and the elimination of the return value, overcomes all the
> following limitations of for_each:
>
> 1. Invocation performance: Execution of Op copy ctor and dtor for
> parameter f.
Often the performance of your version will be worse, as Op is
an empty class which doesn't need to be copied. Or Op is a POD
(say a pointer to function), and the copy as fast as the reference.
Or for_each is inlined, and no copy of the POD is done.
So for_each_ref wil soemtimes be slower than, sometime equivalent
to each_ref, but always very close in term of performance.
So I don't see a clear gain here.
> 2. Return performance: Execution of Op copy ctor and dtor for return
> statement.
Same argument: copying Op is a NOP or very fast. Such
micro-optimisations in a function which is O(n) seem
worthless.
> 3. Assignment: If the client of for_each wishes to use possible changes
> of state of f caused by the invocation, she typically will assign the
> result of for_each to the original argument. Not only is this an
> additional performance hit, but one can easily forget to do it, thus
> giving the wrong answer.
If the result of for_each is used, it make that obvious.
> 4. Object slicing: If for_each is invoked using a reference to an Op,
> which reference actually points to an object of a subclass of Op,
> the wrong behavior will be invoked.
> 5. Copyable objects: Only Op classes which are copyable by clients
> can be used. A class Op fails this condition if its copy ctor is
> declared private or protected, if its copy ctor is declared public
> but not defined, or if its copy ctor is not declared *and* one or
> more data members or base classes of Op is non-copyable.
I think that's a good point, in particular if the object as
an abstract static type.
> "Prefer to pass objects by reference" is a well-known C++ coding
> guideline.
But obviously we want to write:
struct Print;
...
for_each(l.begin (), l.end(), Print());
And a non-const ref won't let you do that. A const ref
won't let you change the object.
I'd like to be able to write:
template <class Arg>
class Functor1 {
public:
virtual void operator() (Arg) const = 0;
};
const Functor1& p = some_functor();
for_each (l.begin (), l.end(), *p);
This way you reduce code size (for_each isn't
a good example as it is moslty trivial, but this
is a general problem).
But an alternative exists:
template <class T>
struct CopyableFunctor1
{
void operator() (Arg) const { f(); }
const Functor1& f;
CopyableFunctor1 (const Functor1&);
};
for_each (l.begin (), l.end(), CopyableFunctor1(some_functor ());
You could also use a ref count, and eventually desallocate
the object, etc...
--
Valentin Bonnard mailto:bonnardv@pratique.fr
info about C++/a propos du C++: http://www.pratique.fr/~bonnardv/
---
[ 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 ]
[ FAQ: http://reality.sgi.com/employees/austern_mti/std-c++/faq.html ]
[ Policy: http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu ]
Author: Oleg Zabluda <zabluda@math.psu.edu>
Date: 1997/12/10 Raw View
inpact3 <inpact3@clr34el.der.edf.fr> wrote:
: Oleg Zabluda wrote:
: >
: > Bill Rubin <rubin@contractor.net> wrote:
: > : It was an unpleasant surprise for me to discover that the function
: > : object (operator) parameter Op of for_each, transform, and
: > : perhaps other STL functions, is passed by value, rather than
: > Matt Austern explained copule of times that if you pass f by non-const
: > reference, then f can't be a const object, and if you pass it by
: > const reference, you can't use f for which operator()() is not
: > a const member. Passing by value is the only choice.
: >
: Maybe I missed something, but I can't see why operator()() isn't a
: const member by requirement. In math, when you apply a function f
: to an argument, that does not change the definition of function f !
Neither does non-const operator()().
: IHMO, I can see only two reasons for making operator() non-const :
: - trying to achieve side-effects, which I consider bad programming style,
: - updating implementation members of the functionoid ; the keyword mutable
: has been introduced for that need.
Using "mutable" would allow other const methods, not only operator()(),
to change the data member.
: Any examples of functionoids for which a non-const operator() is really
: needed are welcome...
Here is one example:
#include <iostream>
#include <algorithm>
using namespace std;
class print{
private:
ostream& os_;
public:
print(ostream& os) : os_(os) {}
void operator()(int i) /* non-const*/ { os_ << i; }
};
int main()
{
int a[3] = {1,2,3};
for_each(a, a+3, print(cout));
return 0;
}
Another example would be a functionoid that caches the results of
recent calculations for recently encountered values of the argument.
Examples are countless.
Oleg.
---
[ 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 ]
[ FAQ: http://reality.sgi.com/employees/austern_mti/std-c++/faq.html ]
[ Policy: http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu ]
Author: Valentin Bonnard <bonnardv@pratique.fr>
Date: 1997/12/10 Raw View
Oleg Zabluda <zabluda@math.psu.edu> writes:
> Here is one example:
>
> #include <iostream>
> #include <algorithm>
>
> using namespace std;
>
> class print{
> private:
> ostream& os_;
> public:
> print(ostream& os) : os_(os) {}
> void operator()(int i) /* non-const*/ { os_ << i; }
> };
>
> int main()
> {
> int a[3] = {1,2,3};
> for_each(a, a+3, print(cout));
> return 0;
> }
>
Nothing prevents operator() from being const here, except
if you change the os_ member to an ostream. An using a
cache is normally expressed with mutable.
And of course you should use ostream_iterator, but you
certainly already know that, and it isn't the point.
--
Valentin Bonnard mailto:bonnardv@pratique.fr
info about C++/a propos du C++: http://www.pratique.fr/~bonnardv/
---
[ 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 ]
[ FAQ: http://reality.sgi.com/employees/austern_mti/std-c++/faq.html ]
[ Policy: http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu ]
Author: Oleg Zabluda <zabluda@math.psu.edu>
Date: 1997/12/10 Raw View
Valentin Bonnard <bonnardv@pratique.fr> wrote:
: Oleg Zabluda <zabluda@math.psu.edu> writes:
: > Here is one example:
: >
: > #include <iostream>
: > #include <algorithm>
: >
: > using namespace std;
: >
: > class print{
: > private:
: > ostream& os_;
: > public:
: > print(ostream& os) : os_(os) {}
: > void operator()(int i) /* non-const*/ { os_ << i; }
: > };
: >
: > int main()
: > {
: > int a[3] = {1,2,3};
: > for_each(a, a+3, print(cout));
: > return 0;
: > }
: >
: Nothing prevents operator() from being const here, except
: if you change the os_ member to an ostream.
You mean to "mutable ostream"? That would be a serious design flaw
since operator()() changes the member, and the change is not just
an implementation detail, but is very visible externally due to
aliasing between os_ and os (their streambuf's). Besides, what do you
do if you don't have a copy constructor? Even in this case you don't,
although you can get around it by writing os_(os.rdbuf()), but not
always you are so lucky. What do you do if a copy constructor has very
undesirable side-effects, like with auto_ptr<>? Or what do you do, if you
want polymorphic behavior? Or what do you do if constructing and/or
storing a whole object, rather then just a reference to it, is just too
much of a performance hit.
: An using a
: cache is normally expressed with mutable.
Maybe, but this is a design decision. Cache change might be considered
to be a change in the logical state of an object. Say, if new values
push out old ones, after your "const" method, your functionoid
performance for some arguments might suffer, which might be considered
to be an "observable event", and a change in the logical constness.
: And of course you should use ostream_iterator, but you
: certainly already know that, and it isn't the point.
Speaking of which. ostream_iterator<> is one more example of a class
for which storing a "mutable" copy, then mutating it (applying non-const
methods to it) within a const method, makes no logical sense whatsoever.
Oleg.
--
Life is a sexually transmitted, 100% lethal disease.
---
[ comp.std.c++ is moderated. To submit articles: Try just posting with your
newsreader. If that fails, use mailto:std-c++@ncar.ucar.edu
comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
Comments? mailto:std-c++-request@ncar.ucar.edu
]
Author: Oleg Zabluda <zabluda@math.psu.edu>
Date: 1997/12/11 Raw View
inpact3 <inpact3@clr34el.der.edf.fr> wrote:
: Oleg Zabluda wrote:
: >
: > Bill Rubin <rubin@contractor.net> wrote:
: > : It was an unpleasant surprise for me to discover that the function
: > : object (operator) parameter Op of for_each, transform, and
: > : perhaps other STL functions, is passed by value, rather than
: > Matt Austern explained copule of times that if you pass f by non-const
: > reference, then f can't be a const object, and if you pass it by
: > const reference, you can't use f for which operator()() is not
: > a const member. Passing by value is the only choice.
: >
: Maybe I missed something, but I can't see why operator()() isn't a
: const member by requirement. In math, when you apply a function f
: to an argument, that does not change the definition of function f !
Neither does non-const operator()().
: IHMO, I can see only two reasons for making operator() non-const :
: - trying to achieve side-effects, which I consider bad programming style,
: - updating implementation members of the functionoid ; the keyword mutable
: has been introduced for that need.
Using "mutable" would allow other const methods, not only operator()(),
to change the data member.
: Any examples of functionoids for which a non-const operator() is really
: needed are welcome...
Here is one example:
#include <iostream>
#include <algorithm>
using namespace std;
class print{
private:
ostream& os_;
public:
print(ostream& os) : os_(os) {}
void operator()(int i) /* non-const*/ { os_ << i; }
};
int main()
{
int a[3] = {1,2,3};
for_each(a, a+3, print(cout));
return 0;
}
Another example would be a functionoid that caches the results of
recent calculations for recently encountered values of the argument.
Examples are countless.
Oleg.
--
Life is a sexually transmitted, 100% lethal disease.
---
[ 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 ]
[ FAQ: http://reality.sgi.com/employees/austern_mti/std-c++/faq.html ]
[ Policy: http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu ]