
I'm not quite sure I understand the behavior of this simple test of BOOST_FOREACH: ------------- #include <vector> #include <iostream> #include <boost/foreach.hpp> using namespace std; struct noisy_vector : public vector<int> { explicit noisy_vector(size_t const sz=0, int const val=0) : vector<int>(sz, val) { cerr << "\tcontainer was constructed" << endl; } noisy_vector(noisy_vector const& other) : vector<int>(other) { cerr << "\tcontainer was copied" << endl; } noisy_vector& operator=(noisy_vector const& other) { vector<int>::operator=(other); cerr << "\tcontainer was assigned" << endl; } ~noisy_vector() { cerr << "\tcontainer was destructed" << endl; } }; inline noisy_vector get_vec(size_t const sz, int const val=0) { return noisy_vector(sz,val); } int main() { cerr << "TEST 1: LVALUE" << endl; { noisy_vector vec = get_vec(5); cerr << "\tstarting foreach" << endl; BOOST_FOREACH(int const& i, vec); } cerr << endl; cerr << "TEST2: LVALUE, REFERENCE" << endl; { noisy_vector const& vec = get_vec(5); cerr << "\tstarting foreach" << endl; BOOST_FOREACH(int const& i, vec); } cerr << "TEST 2: RVALUE" << endl; { cerr << "\tstarting foreach" << endl; BOOST_FOREACH(int const& i, get_vec(5)); } } ------------- If I compile this, using g++ 3.4.4, with the RVO disabled (using -fno-elide-constructors), then I get the following output: ------------- TEST 1: LVALUE container was constructed container was copied container was destructed container was copied container was destructed starting foreach container was destructed TEST2: LVALUE, REFERENCE container was constructed container was copied container was destructed starting foreach container was destructed TEST 2: RVALUE starting foreach container was constructed container was copied container was destructed container was copied container was copied container was destructed container was destructed container was destructed ------------- If I compile it with the RVO enabled, I get the following output: ------------- TEST 1: LVALUE container was constructed starting foreach container was destructed TEST2: LVALUE, REFERENCE container was constructed starting foreach container was destructed TEST 2: RVALUE starting foreach container was constructed container was copied container was destructed container was destructed ------------- So in either case, using BOOST_FOREACH requires one extra copy. Why is this necessary? I guess the macro is making a copy of the argument to avoid evaluating it more than once, but isn't it possible just to bind the temporary to a const reference instead? Thanks... -Lewis

on Thu Jul 05 2007, Lewis Hyatt <lhyatt-AT-princeton.edu> wrote:
I'm not so sure. If you want to avoid seeing the copy that happens when get_vec returns, just do BOOST_FOREACH(int const& i, noisy_vector(5,0)); That's as good an rvalue as any-----^^^^^^^^^^^^^^^^^ and might clarify things for you a bit.
isn't it possible just to bind the temporary to a const reference instead?
That's up to the implementation. At least according to C++98, it's allowed to make a copy when you do that :( -- Dave Abrahams Boost Consulting http://www.boost-consulting.com The Astoria Seminar ==> http://www.astoriaseminar.com

Lewis Hyatt wrote:
I'm not quite sure I understand the behavior of this simple test of BOOST_FOREACH:
<snip>
Let me be more precise: using BOOST_FOREACH *with rvalue containers* requires one "extra" copy. When the container is an lvalue, no copies are made, ever. You suggested binding the rvalue to a const reference. That would work, if you knew both that the object was an rvalue *and* what the type of the object was. But you don't. Instead, you have to put the object in a variant of sorts, and store a const reference to that. That's where the extra copy comes from. For all the details, see: http://www.artima.com/cppsource/foreach.html -- Eric Niebler Boost Consulting www.boost-consulting.com

OK, thanks, I did read your article first, it's very informative, but I felt that it never considered the possibility of binding to a const reference instead of making a copy. I guess, once the auto keyword is available, then you could probably avoid the copy, although you might have to make two macros, one that allows the container to be altered, and one that doesn't, or something like that. Anyway, I guess this isn't such a big issue, because if copying a container is too expensive, you probably shouldn't be returning it from functions anyway... -Lewis

Lewis Hyatt skrev:
On the contrary, copying can be essentially free because of the Return Value Optimization (RVO). Eric, maybe there should be an extra macro: BOOST_RVALUE_FOREACH( const T& x, get_rvalue_container() ) which does not make the copy? At the call site, you usually know which macro to apply. -Thorsten

On 7/6/07, Lewis Hyatt <lhyatt@princeton.edu> wrote:
BTW, couldn't BOOST_FOREACH internally detect if the container has a swap member function and swap the container instead of copying it: [here I assume that auto_any constructor is the source of the extra copy, but I'm not 100% sure] template<typename T, bool IsSwappable> struct auto_any : auto_any_base { auto_any(T& t) : item(t) { } mutable T item; }; template<typename T> struct auto_any <T, true>: auto_any_base { auto_any(T t) : item() { item.swap(t); } mutable T item; }; The tricky part here is that this assumes that swap does the obvious thing and T is default constructible. The former is a sensible thing to expect from a container, I'm not convinced of the latter. OTOH, this might work for non copyable but swappable containers. AFAIK, while detecting the presence of T::swap is possible [1], detecting default constructibility isn't.
I think that today, with NRVO widely implemented, this is no longer a problem. gpd [1] http://tinyurl.com/2abdy5
participants (5)
-
David Abrahams
-
Eric Niebler
-
Giovanni Piero Deretta
-
Lewis Hyatt
-
Thorsten Ottosen