11

The new range-based for loops really improve readability and are really easy to use. However, consider the following :

map<Foo,Bar> FooAndAssociatedBars;

for (auto& FooAndAssociatedBar : FooAndAssociatedBars) {
    FooAndAssociatedBar.first.doSth();
    FooAndAssociatedBar.second.doSomeOtherThing();
}

It may be a detail but I find it would have been more readable if I could have done something like :

for ( (auto& foo, auto& bar) : FooAndAssociatedBars) {
    foo.doSth();
    bar.doSomeOtherThing();
}

Do you know an equivalent syntax ?

EDIT: Good news: C++17 has a proposal that adresses this problem, called structured bindings (see 1). In C++17, you should be able to write:

tuple<T1,T2,T3> f(/*...*/) {
    /*...*/ 
    return {a,b,c};
}
auto [x,y,z] = f(); // x has type T1, y has type T2, z has type T3

which solves this readability problem

Bérenger
  • 2,678
  • 2
  • 21
  • 42
  • How would the compiler guess what `Foo` and `Bar` were meant to refer to? – Pete Becker Mar 20 '13 at 14:50
  • @PeteBecker Well, foo is just a convenient way to say "name foo the left part of the std::pair". In absolute terms, it is possible to do this at compile time : this is just a notational convenience. I was wondering if it was possible to get such effect via overloading or something like that – Bérenger Mar 20 '13 at 15:05
  • Yes, it's certainly possible to require the compiler to know about `std::pair` or to look for any struct with elements named `first` and `second`; that's rather specialized, and probably not appropriate for standardization. The next request would be for all the elements of a `tuple`... – Pete Becker Mar 20 '13 at 15:15
  • 2
    @Pete: The OP's request does not seem so unreasonable (well, except for the type inference maybe): it is not so different than using `std::tie` to unpack a pair/tuple. The syntax could mean "define variables `foo` and `bar` before the loop (in this case as reference wrappers), and on each iteration do `tie(foo, bar) = *it`". It could be used in other places as well, for instance for unpacking the results of a function returning multiple values in a tuple: `(bool inserted, set::iterator it) = mySet.insert(42);`. [...] – Luc Touraille Mar 20 '13 at 15:31
  • [...] I don't see any technical difficulties preventing this, but I guess tuples are not as ubiquitous in C++ as they are in other languages, so such syntax sugar is probably not essential. – Luc Touraille Mar 20 '13 at 15:31
  • 1
    @LucTouraille - `std::tie` is in the library, not the compiler. So this approach, too, requires the compiler to know more details about the standard library. – Pete Becker Mar 20 '13 at 15:33
  • 3
    Well, I used `std::tie` only as an example of a possible implementation (that doesn't even work), but a compiler would be free to generate any code to provide this behavior. `begin` and `end` are also in the library, yet they are used to provide the range-based for loop syntax. – Luc Touraille Mar 20 '13 at 15:37
  • 3
    The syntax could be made to work with any type that support `std::get` (pairs, tuples, arrays, ...). The OP's code could be transformed into `for (auto & tmp : FooAndAssociatedBars) { auto & foo = std::get<0>(tmp); auto & bar = std::get<1>(tmp); ... }`; the sample code I gave could be transformed similarly: `auto & tmp = mySet.insert(42); bool inserted = std::get<0>(tmp); set::iterator it = std::get<1>(tmp);`. This would even allow type inference. – Luc Touraille Mar 20 '13 at 15:56
  • @LucTouraille Yes, I was thinking of something of that kind. I still don't know if we can do it but we are getting closer... – Bérenger Mar 20 '13 at 20:01
  • @LucTouraille: Maybe you should write your thoughts into a standard proposal ;) – Grizzly Mar 23 '13 at 20:42
  • I understand what you're saying.. `first` and `second` aren't particularly useful names. All you can do is use `const &` type assignments in the first 2 lines of your loop, like: `for( pair& p : m ) { const int& foo = p.first ; int& bar = p.second ; }` – bobobobo Dec 20 '13 at 19:57

3 Answers3

10

There is no such thing as you want. The closest is to declare variables inside the loop:

for (auto& FooAndAssociatedBar : FooAndAssociatedBars) {
    auto& foo = FooAndAssociatedBar.first;
    auto& bar = FooAndAssociatedBar.second;

    // ...
}
Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
1

Not a good idea. Sooner or later, you would want the same for a std::tuple, and compiler should be able to use std::get<> on the tuple automatically. In my opinion your approach is pleasing you at the moment only, and you would find problems with this approach (assume it is implemented that way).

Standard committee has designed range-based for-loop with deep consideration. It is way better than foreach loop in other languages, and it is way shorter. Couple it with auto& and you are done!

Ajay
  • 18,086
  • 12
  • 59
  • 105
  • 1
    "Not a good idea. Sooner or later, you would want the same for a std::tuple, and compiler should be able to use std::get<> on the tuple automatically." And the problem with that is? – Cubic Mar 20 '13 at 17:12
  • More demands from the compiler. – Ajay Mar 20 '13 at 17:36
  • How so? The way I see it any such language feature would be defined to call `std::get` (well unqualified `get` more likely) and therefore support `std::pair` and `std::tuple` and any user defined `tuple` like containers automatically. – Grizzly Mar 23 '13 at 20:40
  • Exactly, that's my point. You are demanding too much from compiler. And it adds complexity, ambiguity, more complex error messages, fight between different compiler vendors. – Ajay Mar 24 '13 at 06:38
0

And of course, you always have the possibility to use lambdas.

std::map<int, const char*> m { { 4, "hello" }, { 11, "c++" } };
convenient_for_each(m, [](int a, const char* b) {
    std::cout << b << a << std::endl;
  });
convenient_for_each(m, [](std::pair<int, const char> p) {
    std::cout << p.first << p.second << std::endl;
  });

Or wrapped as macro (not recommended)

FOREACH((int a, const char* b), m, std::cout << a << b << std::endl);
FOREACH((std::pair<int, const char*> p), m, std::cout << p.first << p.second << std::endl);

(Hackish sample implementation at LWS)

Auto won't work though, I'm still waiting for polymorphic lambdas. My approach is theoretically able to handle tuples as well.

ipc
  • 8,045
  • 29
  • 33