30

C++17 standard introduces a new structured bindings feature, which was initially proposed in 2015 and whose syntactic appearance was widely discussed later.

Some uses for them come to mind as soon as you look through documentation.

Aggregates decomposition

Let's declare a tuple:

std::tuple<int, std::string> t(42, "foo");

Named elementwise copies may be easily obtained with structured bindings in one line:

auto [i, s] = t;

which is equivalent to:

auto i = std::get<0>(t);
auto s = std::get<1>(t);

or

int i;
std::string s;
std::tie(i, s) = t;

References to tuple elements can also be obtained painlessly:

auto& [ir, sr] = t;
const auto& [icr, scr] = t;

So we can do with arrays or structs/classes whose all members are public.

Multiple return values

A convenient way to get multiple return values from a function immediately follows from the above.

What else?

Can you provide some other, possibly less obvious use cases for structured bindings? How else can they improve readability or even performance of C++ code?

Notes

As it were mentioned in comments, current implementation of structured bindings lacks some features. They are non-variadic and their syntax does not allow to skip aggregate members explicitly. Here one can find a discussion about variadicity.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
Sergey
  • 7,985
  • 4
  • 48
  • 80
  • 3
    having read the answers... basically there are none. It's just syntactic sugar. Since you can't make them variadic, there's nothing fun or impressive to be done around variadic template expansion either. – Richard Hodges Aug 03 '17 at 10:14
  • @RichardHodges Well, I agree with you, more or less. A lot of features recently introduced are syntactic sugar at the end of the day. – skypjack Aug 03 '17 at 10:22
  • @RichardHodges Even though it's not magic, Vittorio Romeo's answer provides an example of a useful feature implemented with them. Before variadic templates became available, a lion's share of boost library was implemented like that. By the way, is there a proposal of variadic structured bindings? Is it conceptually possible? Won't it contradict already existing standard? – Sergey Aug 03 '17 at 10:22
  • 1
    The only problem I see with structured bindings, is that one cannot "ignore" any member of the aggregate. If to contrast this with Python's iterable unpacking, it seems slightly less useful. – StoryTeller - Unslander Monica Aug 03 '17 at 10:23
  • @StoryTeller this also looks like a good reason to write a proposal. – Sergey Aug 03 '17 at 10:27
  • 1
    @Sergey That's nothing more than a way to unpack a struct in a set of variables. Not that funny or impressive actually. – skypjack Aug 03 '17 at 10:28
  • 1
    @Sergey - Indeed. And yet I don't see how it can be accomplished. In Python it's done by just binding repeatedly to an identifier one easily ignores, like `_`. But one cannot do that in C++ of course. The grammar will become terribly complex for a very niche use. – StoryTeller - Unslander Monica Aug 03 '17 at 10:30
  • @StoryTeller You could explore the other way around and try to propose a tag or whatever to discard the i-th element. Not exactly the same, but at least a compiler could optimize it at compile time and you wouldn't have a variable that contains something in which you are not interested. Would it be possible? – skypjack Aug 03 '17 at 10:36
  • I think the better question would be how often do these use cases arise? Considering existing C++ syntax complexity it is surprising to see even more fancy rules being squeezed in. – user7860670 Aug 03 '17 at 10:37
  • 1
    @skypjack - A tag as in a new keyword? I suppose it could work perfectly. But the committee seems very skittish about changing the keyword set of the language. – StoryTeller - Unslander Monica Aug 03 '17 at 10:38
  • 1
    @StoryTeller why not to use an existing keyword? For example, `[a, void, b] = foo();`. Of course, it looks inconsistent as `void` is a typename, while others are identifiers, but it's not the first such place in C++ syntax. – Sergey Aug 03 '17 at 10:40
  • @Sergey - I both love and hate it at the same time. This *may* be proposal worthy. – StoryTeller - Unslander Monica Aug 03 '17 at 10:41
  • @StoryTeller Actually a tag isn't necessary, it would be enough being able to do something like this: `[a, , b] = foo()`. Anyway I think the standard would collapse on itself if you put something like this inside it!! :-D – skypjack Aug 03 '17 at 10:44
  • 1
    @skypjack - I suppose that could work as well, but my problem with that is that it looks too much like a typo. Using `void`, despite the WTF factor, still seems more intentional. – StoryTeller - Unslander Monica Aug 03 '17 at 10:45
  • 1
    @StoryTeller I agree, both on the fact that is seems intentional and on the fact that it has the greatest WTF factor ever. Anyway, you must know in any case that there exists something there, so one can still put it in a variable and `(void)it`. The compiler will probably optimize it out and the result will be exactly the same. So, again, we are probably discussing of syntactic sugar. – skypjack Aug 03 '17 at 10:48
  • @skypjack - Completely. But I don't think it's the bad sort of syntactic sugar. If it makes common code simpler to write (like range for did for all the iterator boilerplate) it's welcome in my book. – StoryTeller - Unslander Monica Aug 03 '17 at 10:50
  • @StoryTeller I agree. Like fold expressions, structured bindings and so on. We can probably leave without them, of course but give me those tools and I'll be happy with them too. ;-) – skypjack Aug 03 '17 at 10:54
  • 6
    You're asking to just list uses of a language feature? That's the epitome of "too broad." – Barry Aug 03 '17 at 12:17
  • 2
    Agree that the question could be more specific although perhaps not too broad if more concisely worded. I would point out that once-upon-a-time people didn't think much of design patterns either. Is there something in the spec that points to the need for the addition of structured binding? – Raydot Aug 03 '17 at 18:38

4 Answers4

25

Can you provide some other, possibly less obvious use cases for structured bindings? How else can they improve readability or even performance of C++ code?

More in general, you can use it to (let me say) unpack a structure and fill a set of variables out of it:

struct S { int x = 0; int y = 1; };

int main() {
    S s{};
    auto [ x, y ] = s;
    (void)x, void(y);
}

The other way around would have been:

struct S { int x = 0; int y = 1; };

int main() {
    S s{};
    auto x = s.x;
    auto y = s.y;
    (void)x, void(y);
}

The same is possible with arrays:

int main() {
    const int a[2] = { 0, 1 };
    auto [ x, y ] = a;
    (void)x, void(y);
}

Anyway, for it works also when you return the structure or the array from a function, probably you can argue that these examples belong to the same set of cases you already mentioned.


Another good example mentioned in the comments to the answer by @TobiasRibizel is the possibility to iterate through containers and unpack easily the contents.
As an example based on std::map:

#include <map>
#include <iostream>

int main() {
    std::map<int, int> m = {{ 0, 1 }, { 2, 3 }};
    for(auto &[key, value]: m) {
        std::cout << key << ": " << value << std::endl;
    }
}
skypjack
  • 49,335
  • 19
  • 95
  • 187
10

Can you provide some other, possibly less obvious use cases for structured bindings?

They can be used to implement get<N> for structs - see magic_get's automatically generated core17_generated.hpp. This is useful because it provides a primitive form of static reflection (e.g. iterate over all members of a struct).

Vittorio Romeo
  • 90,666
  • 33
  • 258
  • 416
2

Initializing multiple variables of different types in an if statement; for instance,

if (auto&& [a, b] = std::pair { std::string { "how" }, 4U }; a.length() < b)
   std::cout << (a += " convenient!") << '\n';
plexando
  • 1,151
  • 6
  • 22
1

Barring evidence to the contrary, I think Structured Bindings are merely a vehicle to deal with legacy API. IMHO, the APIs which require SB should have been fixed instead.

So, instead of

auto p = map.equal_range(k);
for (auto it = p.first; it != p.second; ++it)
    doSomethingWith(it->first, it->second);

we should be able to write

for (auto &e : map.equal_range(k))
    doSomethingWith(e.key, e.value);

Instead of

auto r = map.insert({k, v});
if (!r.second)
    *r.first = v;

we should be able to write

auto r = map.insert({k, v});
if (!r)
    r = v;

etc.

Sure, someone will find a clever use at some point, but to me, after a year of knowing about them, they are still an unsolved mystery. Esp. since the paper is co-authored by Bjarne, who's not usually known for introducing features that have such a narrow applicability.

Marc Mutz - mmutz
  • 24,485
  • 12
  • 80
  • 90
  • 1
    C++17 users can get far tidier code out of existing API, something fated to change slowly, without changing it & foisting rewrites on those who were already happy(!) with all the `pair` horror. So long as we're stuck with the existing API, I'm glad to be able to tidy up such code. It's also a step closer to (looking like) multiple return values for user functions (using better types than `pair`); the absence of those never bothered me too much, but I get the impression it's something other people are quite enthusiastic about. Of course, compiler authors may tend to agree with you more than us! – underscore_d Sep 16 '18 at 18:20
  • fwiw, imho `if (!r) *r = v;` would be more clear, while imho `if (!r) r = v;` is confusing. – 463035818_is_not_an_ai Jun 21 '21 at 08:19