Can I use structured bindings and a for-each loop to iterate through a few “packed-together” values?

Viewed 315

I often use initializer lists and for-each loops to iterate through a small number of ad-hoc values, like so:

for (auto x : {1, 2, 6, 24, 120}) {
  do_something(x);
}

I recently tried to write something similar, but with structured bindings and packed-together values instead:

for (auto[dx, dy] : {{-1, 0}, {1, 0}, {0, -1}, {0, 1}}) {
  try_to_move(dx, dy); // nope! won’t compile
}

Unfortunately, this doesn’t compile. Clang tells me:

error: cannot use type ‘void’ as a range

In fact, even something like auto mylist = {{1, 2}, {3, 4}}; won’t compile.

This leaves me with two questions:

  1. Is there an alternative syntax to accomplish what I want in a terse and readable manner?

  2. Why doesn’t the type of auto mylist get parsed as initializer_list<initializer_list<int>>? Wouldn’t that work fine?

2 Answers

Why doesn’t the type of auto mylist get parsed as initializer_list<initializer_list<int>>? Wouldn’t that work fine?

The reason is that simply no-one proposed it yet.

The handy syntax auto x = {1, 2, 6, 24, 120}; comes from proposal N3912 which was adopted into C++17 (see also N3922).

The deduction process is outlined in [dcl.type.auto.deduct]/4:

If the placeholder is the auto type-specifier, the deduced type T' replacing T is determined using the rules for template argument deduction. Obtain P from T by replacing the occurrences of auto with either a new invented type template parameter U or, if the initialization is copy-list-initialization, with std​::​initializer_­list<U>. Deduce a value for U using the rules of template argument deduction from a function call, where P is a function template parameter type and the corresponding argument is e. If the deduction fails, the declaration is ill-formed.

Since type deduction in a hypothetical function call f({1, 2}) would fail, so too is the nested braced-init-list deduction auto x = { {1, 2}, {3, 4} }; also impossible.

I guess the same trick could be applied to a deduction from a function call, which would make nested initializer_list deduction possible.

So a follow-up proposal is welcome.

Is there an alternative syntax to accomplish what I want in a terse and readable manner?

You could define a good old multidimensional array:

int lst[][2] = { {1, 2}, {3, 4}, {5, 6}, {7, 8} };
for (auto [x, y] : lst) {
    . . .
}

Or as suggested in the comments, give the first pair a type to help the deduction:

for (auto [x, y] : { std::pair{1, 2}, {3, 4}, {5, 6}, {7, 8} }) {
    . . .
}

You can use it like this. You just need to initialize your ad-hoc list to a variable first.

vector<pair<int, int> > p = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
for (auto x : p) {
    try_to_move(x);
}

You then access the parameter x like this in the function

<return-type> try_to_move(pair<int, int> x){
    int dx = x.first;
    int dy = x.second;
    // TO-DO
}
Related