0

Here is an example of C++ range based loop that captures elements by value

vector<int> v = {1, 3, 5, 7, 9};

for (auto x : v)
    cout << x << ' ';

More here What is the correct way of using C++11's range-based for?


My question

Why is capturing by value even allowed? It is just confusing and error prone. Costs you a lot if make the mistake. I can't imagine anything that can not be solved by just creating a copy explicitly instead of the loop doing it for you. Is there any specific reason? Am I missing something here?

EDIT

Yes I know pass by value is allowed for functions, But that has a clear benefit to it. What I am asking is if there is any use of capturing by value in a for loop. I can see now that this can be useful sequences of bools and chars.

Community
  • 1
  • 1
  • 2
    What about `vector`? The overhead of reference is significant in this case. Also, that's just how `auto` works. – Bartek Banachewicz Apr 10 '13 at 13:46
  • 2
    @BartekBanachewicz `vector` FTFY. ;-) – Konrad Rudolph Apr 10 '13 at 13:46
  • 2
    Why is pass by value allowed at all? – juanchopanza Apr 10 '13 at 13:49
  • 1
    How is it any more error prone than any other copy? i.e. `int x = v.begin();`? – Vaughn Cato Apr 10 '13 at 13:50
  • @juanchopanza because pass by value is usefull. Can you tell me a use of this other than for char and bool mentioned by the others? –  Apr 10 '13 at 13:54
  • 3
    You tell me. Why is pass by value useful? – R. Martinho Fernandes Apr 10 '13 at 13:57
  • possible duplicate of [What is the correct way of using C++11's range-based for?](http://stackoverflow.com/questions/15927033/what-is-the-correct-way-of-using-c11s-range-based-for) – Suma Apr 10 '13 at 13:59
  • 1
    So, pass by value is useful, that much is clear. Then getting the elements of a range by value is also useful. – juanchopanza Apr 10 '13 at 14:00
  • @juanchopanza Here is the thing man, this is not the way for loops worked pre C++11. Now it is added. Together with a bunch of useful things. I am just asking if there is a reason for this addition, which makes it very different from how for loops used to work before. That is what I am wondering about. –  Apr 10 '13 at 14:03
  • If you're iterating over a `vector` I prefer to use `auto` instead of `const auto &` since it's shorter, and the resulting code will most likely be the same, so why type more? – mfontanini Apr 10 '13 at 14:06
  • @stardust_: "By value" is *always* the default in C++. You have to explicitly ask for "by reference" for anything, whether parameters or whatever. – Nicol Bolas Apr 10 '13 at 15:20

2 Answers2

7

Exactly the same reason we can pass arguments by value - if you need a copy, take it by value.

I can't imagine anything that can not be solved by just creating a copy explicitly instead of the loop doing it for you.

I think this is the main issue you're having. The loop isn't "doing it for you". This is asking for a copy explicitly. What's more explicit than initialising a non-reference variable?

This is really just a normal declaration. Why, when it is valid everywhere else, would we make auto by itself be invalid here? In fact, the initialization of this declaration is defined by the standard as:

auto x = *__begin;

where __begin is the expression giving an iterator to the first element of the range (in this case v.begin()). This is no different to any other copying in C++. Would you consider the following to be a common mistake?

int x = some_other_int;

Or:

std::string str = some_other_string;

No, we write a declaration like this when we want a copy.

Here's an example use case:

void modify_argument(X&);
void use(X);

// ...

std::vector<X> v = /* ... */;
for (auto x : v) {
  // We want to modify the copy of x, but not the original:
  modify_argument(x);
  use(x);
}
Joseph Mansfield
  • 108,238
  • 20
  • 242
  • 324
  • Here is the thing. It is not bad practice to disallow things that are not useful. Pass by value for functions is useful. Yes. Do you know any cases where this is useful? any use case? I got some on the comments. –  Apr 10 '13 at 13:57
  • @stardust_ It's useful when you want a copy of the element and not a reference to it. Exactly the same as passing an argument by reference. – Joseph Mansfield Apr 10 '13 at 13:58
  • 1
    Hmm. Ya i think it is more a matter of consistency rather than use. OK. I got it. –  Apr 10 '13 at 13:59
  • Why do you modify _a copy of a copy of x_? You create a copy of the vector element (=`x`) and pass it by reference to `modify_argument()`. –  Apr 10 '13 at 14:16
  • @stardust_: You seem to assume that *pass-by-value* is useful in a way that this is not. How? You can always *pass-by-reference* to a function and let the implementation copy if it needs to, right? Is that not what you are suggesting for the loop? How is doing that inside a function different than doing it in the loop? – David Rodríguez - dribeas Apr 10 '13 at 14:32
  • @stardust_ I don't see how I am. `*v.begin()` returns a reference to the object inside the vector. It is then copied into `x`. – Joseph Mansfield Apr 10 '13 at 15:33
  • @stardust_ I added a bit to my answer about how this *is* asking for it explicitly. – Joseph Mansfield Apr 10 '13 at 15:51
2

I think part of the confusion arises due to the use of auto, which is common although not mandatory. Basically the range-based for for (type var : range) means: iterate over the elements in range creating a local variable var of type type initialized with the respective element in the range. This construct has a direct translation in term of the regular for loop, mandated by the standard:

// for (for-range-declaration : range-init)
//    statement
{
  auto && __range = range-init;
  for ( auto __begin = begin-expr, __end = end-expr;
            __begin != __end; ++__begin ) {
     for-range-declaration = *__begin;
     statement
  }
}

The range-init and for-range-declaration can be anything that can be replaced on the expanded version and compile. There is no requirement on what the type in the for-range-declaration is, and it can even be something different from what the range-init holds:

// Convoluted example:
void printFloor(std::vector<double> const & v) {
   for (int i : v) {
       std::cout << ' ' << i;
   }
}

Because any type can be used that allows for the expansion to compile, auto is allowed, with exactly the same semantics that it has in any other context, and the behavior is exactly the same as in all other situations.

Changing the requirements for the range-based for to only allow iterating by reference would unnecessarily complicate the language (standard wording) and the implementation (the compiler cannot just expand, as the for-range-declaration would not be just a declaration, but a limited form that inhibited the use of values) and actually limit the uses of the construct requiring more complex user code when a value is needed (the user would have to manually copy). Keep in mind that this construct is there only to simplify the code, it is not an enabling feature, there is nothing that can be done with this construct that cannot be done without it (you can always manually produce the expansion above).

David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489