15

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?

Tim Sylvester
  • 22,897
  • 2
  • 80
  • 94
  • How beneficial or possible RVO is probably depends on your definition of `Joiner`. That said, are you really willing to give up the simple and clean syntax `Foo const x = std::for_each(v.begin(), v.end(), Joiner());` with something possibly much uglier? – GManNickG Feb 07 '10 at 05:39
  • for_each/copy should be returning a Joiner, not a Foo, right? Your example confuses me. Unless you're trying to imply Joiner is convertible to Foo? – Terry Mahaffey Feb 07 '10 at 05:43
  • 2
    I wrote three different answers to prove you are wrong and eventually understood that you are not. Very helpful, thank you for the question. +1. – P Shved Feb 07 '10 at 10:29
  • @GMan Well that's the point. I want the nice syntax and the full range of optimizations, but I can't figure out how to make it work, thus the question. – Tim Sylvester Feb 07 '10 at 21:42
  • @Tery Yes, the class is convertable to the templated type via an `operator T()`. Actually, in one case the template is derived from the templated type and so does not need that. – Tim Sylvester Feb 07 '10 at 21:42

5 Answers5

15

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
  • 11,775
  • 1
  • 35
  • 44
  • 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 Feb 07 '10 at 07:17
  • Nope, looks the same in C++0x. I think it's "by design" (non-restrictive language giving more freedom to library implementors) – Terry Mahaffey Feb 07 '10 at 07:21
  • 1
    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 Feb 07 '10 at 21:23
4

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.

Arzar
  • 13,657
  • 3
  • 25
  • 25
  • 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 Feb 07 '10 at 22:07
  • 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. – Arzar Feb 08 '10 at 18:29
2

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.

1

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.

Community
  • 1
  • 1
dirkgently
  • 108,024
  • 16
  • 131
  • 187
  • @Tim Sylvester: Updated my answer. – dirkgently Feb 07 '10 at 21:48
  • 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 Feb 07 '10 at 22:33
  • @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 Feb 07 '10 at 22:37
0

For a solution that will work with pre-c++11 code, you may consider using boost::function along with boost::ref(as boost::reference_wrapper alone doesn't has an overloaded operator(), unlike std::reference_wrapper which indeed does). From this page http://www.boost.org/doc/libs/1_55_0/doc/html/function/tutorial.html#idp95780904, you can double wrap your functor inside a boost::ref then a boost::function object. I tried that solution and it worked flawlessly.

For c++11, you can just go with std::ref and it'll do the job.

Community
  • 1
  • 1
Mohammed Safwat
  • 191
  • 2
  • 8