views:

371

answers:

4

I have a class which accumulates information about a set of objects, and can act as either a functor or an output iterator. This allows me to do things like:

std::vector<Foo> v;
Foo const x = std::for_each(v.begin(), v.end(), Joiner<Foo>());

and

Foo const x = std::copy(v.begin(), v.end(), Joiner<Foo>());

Now, in theory, the compiler should be able to use the copy elision and return-value optimizations so that only a single Joiner object needs to be created. In practice, however, the function makes a copy on which to operate and then copies that back to the result, even in fully-optimized builds.

If I create the functor as an lvalue, the compiler creates two extra copies instead of one:

Joiner<Foo> joiner;
Foo const x = std::copy(v.begin(), v.end(), joiner);

If I awkwardly force the template type to a reference it passes in a reference, but then makes a copy of it anyway and returns a dangling reference to the (now-destroyed) temporary copy:

x = std::copy<Container::const_iterator, Joiner<Foo>&>(...));

I can make the copies cheap by using a reference to the state rather than the state itself in the functor in the style of std::inserter, leading to something like this:

Foo output;
std::copy(v.begin(), v.end(), Joiner<Foo>(output));

But this makes it impossible to use the "functional" style of immutable objects, and just generally isn't as nice.

Is there some way to encourage the compiler to elide the temporary copies, or make it pass a reference all the way through and return that same reference?

+10  A: 

You have stumbled upon an often complained about behavior with <algorithm>. There are no restrictions on what they can do with the functor, so the answer to your question is no: there is no way to encourage the compiler to elide the copies. It's not (always) the compiler, it's the library implementation. They just like to pass around functors by value (think of std::sort doing a qsort, passing in the functor by value to recursive calls, etc).

You have also stumbled upon the exact solution everyone uses: have a functor keep a reference to the state, so all copies refer to the same state when this is desired.

I found this ironic:

But this makes it impossible to use the "functional" style of immutable objects, and just generally isn't as nice.

...since this whole question is predicated on you having a complicated stateful functor, where creating copies is problematic. If you were using "functional" style immutable objects this would be a non-issue - the extra copies wouldn't be a problem, would they?

Terry Mahaffey
+1 for noticing the irony.
MSN
Do you know if any work has been done to fix this in C++0x. I've found it very annoying too. In fact, my library implementation (at the time) insisted that functors be default constructable so there was really no way I could have any state at all in them.
Omnifarious
Nope, looks the same in C++0x. I think it's "by design" (non-restrictive language giving more freedom to library implementors)
Terry Mahaffey
You make a good point, but I'm using state internally to allow callers to use a more functional pattern. It's not as if functional programming languages avoid using state in their C implementations, not to mention "monads." Can you suggest a better pattern for allowing the sort of call that I outlined?
Tim Sylvester
A: 

RVO is just that -- return value optimization. Most compilers, today, have this turned-on by default. However, argument passing is not returning a value. You possibly cannot expect one optimization to fit in everywhere.

Refer to conditions for copy elision is defined clearly in 12.8, para 15, item 3.

when a temporary class object that has not been bound to a reference (12.2) would be copied to a class object with the same cv-unqualified type, the copy operation can be omitted by constructing the temporary object directly into the target of the omitted copy

[emphasis mine]

The LHS Foo is const qualified, the temporary is not. IMHO, this precludes the possibility of copy-elision.

dirkgently
@Tim Sylvester: Updated my answer.
dirkgently
Indeed explicitly declaring the return type as a non-const value of the functor type does cause RVO to kick in. That certainly explains it, though it seems strange that you should have to declare an additional auto variable in order for the compiler to optimize away a copy. In my original example, the value being assigned is not the functor type. I would have thought that the unnamed temporary return value could be the target of an RVO since its implicit type would be the same as the r-value parameter.
Tim Sylvester
@Tim Sylvester: Thanks for checking this out. As I marked out, it *is* about exact types and I can't find anything that says any conversion is done (which IMO doesn't make sense in a copy operation -- you are copying values among objects of _same_ type).
dirkgently
Why the downvote?
dirkgently
A: 

If you have a recent compiler (At least Visual Studio 2008 SP1 or GCC 4.4 I think) you can use std::ref/std::cref

#include <string>
#include <vector>
#include <functional> // for std::cref
#include <algorithm>
#include <iostream>

template <typename T>
class SuperHeavyFunctor 
{
    std::vector<char> v500mo;
    //ban copy
    SuperHeavyFunctor(const SuperHeavyFunctor&);
    SuperHeavyFunctor& operator=(const SuperHeavyFunctor&);
public:
    SuperHeavyFunctor():v500mo(500*1024*1024){}
    void operator()(const T& t) const { std::cout << t << std::endl; }
};

int main()
{
    std::vector<std::string> v; v.push_back("Hello"); v.push_back("world");
    std::for_each(v.begin(), v.end(), std::cref(SuperHeavyFunctor<std::string>()));
    return 0;
}

Edit : Actually, the MSVC10's implementation of reference_wrapper don't seem to known how to deduce the return type of function object operator(). I had to derive SuperHeavyFunctor from std::unary_function<T, void> to make it work.

Thomas Petit
I tried this with `boost::ref` and it doesn't work. With `std::tr1::ref` (MSVC9) it works for those algorithms expecting a functor like `for_each`, but not for those expecting an iterator like `copy`. Perhaps I should abandon the use of `copy` for this purpose. Thanks!
Tim Sylvester
Yes, sorry I should have add that boost::ref doesn't work for this case. That's because boost::reference_wrapper operator() doesn't forward to the underlying function object operator() (boost::reference_wrapper doesn't even have an operator() :)And about the function object decaying to an output iterator... well it's the first time I heard a design like that. It sound pretty weird.
Thomas Petit
+4  A: 

Just a quick note, for_each, accumulate, transform (2nd form), provide no order guarantee when traversing the provided range.

This makes sense for implementers to provide mulit-threaded/concurrent versions of these functions.

Hence it is reasonable that the algorithm be able to provide an equivalent instance (a new copy) of the functor passed in.

Be wary when making stateful functors.

Beh Tou Cheh