Un nouvel article traitant des nouveautés du C++17. Aujourd’hui, nous allons présenter les structured bindings, introduits avec la proposition P0144R0 puis acceptés en avec la P0217R3.
Le principe
Les structured bindings constituent une nouvelle syntaxe pour la manipulation d’ensembles de variables (tels std::tuple ). Ils permettent d’initialiser plusieurs variables à partir d’un ensemble ainsi que de faciliter les retours multiples.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#include <tuple> #include <string> #include <iostream> auto foo() { return std::make_tuple(0, 42., "Hello"); } int main() { auto result = foo(); // Récupération des éléments du tuple. int i = std::get<0>(result); double d = std::get<1>(result); std::string s = std::get<2>(result); std::cout << i << ", " << d << ", " << s << '\n'; } |
1 |
0, 42, Hello |
Dans l’exemple précédant, la fonction foo retourne trois élément à l’aide d’un std::tuple . std::tie , introduit avec C++11, simplifie l’initialisation des variables locales à partir du tuple :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#include <tuple> #include <string> #include <iostream> auto foo() { return std::make_tuple(0, 42., "Hello"); } int main() { // Récupération des éléments du tuple. int i; double d; std::string s; std::tie(i, d, s) = foo(); std::cout << i << ", " << d << ", " << s << '\n'; } |
Un gros défaut de std::tie est qu’il oblige le programmeur à repousser l’initialisation des variables, on constate que i , d et s sont ici déclarées sans initialisation. De plus, si on souhaite utiliser des constantes ou des références, il ne sera pas possible de les binds, l’initialisation doit être faite à la déclaration.
La syntaxe est un peu plus courte qu’avec std::get et il est maintenant possible de se passer de la variable result (en effet, cette variable temporaire était utilisée pour éviter d’effectuer plusieurs appels à foo et donc de créer plusieurs fois le tuple).
Dans ce cas, les structured bindings peuvent être vu comme une syntaxe simplifiée pour std::tie :
1 2 3 4 5 6 7 8 9 10 11 |
#include <tuple> #include <string> #include <iostream> auto foo() { return std::make_tuple(0, 42., "Hello"); } int main() { auto [i, d, s] = foo(); std::cout << i << ", " << d << ", " << s << '\n'; } |
1 |
0, 42, Hello |
Les variables sont directement initialisées contrairement à la solution précédante. L’inférence de type (avec auto,)de déduire chaque élément du std::tuple (utilisation des règles de déduction habituelles : const, spécificateur de référence &, &&).
Notez que contrairement à std::tie , les structured bindings autorisent la récupération de références sur les éléments du tuple :
1 2 3 4 5 6 |
auto tuple = std::make_tuple(12, U'a'); auto& [i, c] = tuple; i += c; std::cout << "i = " << std::get<0>(tuple) << '\n'; |
1 |
i = 109 |
Un exemple concret
L’exemple suivant présente un cas classique d’utilisation des structured bindings avec std::map :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
#include <tuple> #include <string> #include <map> #include <iostream> int main() { auto phone_book = std::map<std::string, std::string>{ {"Jean Dupont", "0625447896"}, {"Chuck Norris", "0601234567"}, {"James Bond", "007"}, {"Jim Raynor", "koprulu-014:487b-4689"} }; for (const auto& [name, phone_number] : phone_book) std::cout << name << " [" << phone_number << "]" << '\n'; } |
1 2 3 4 |
Chuck Norris [0601234567] James Bond [007] Jean Dupont [0625447896] Jim Raynor [014:487b-4689] |
Les structured bindings permettent ici de récupérer la clef et la valeur du std::map directement.
Les structures et les arrays
Les structured bindings fonctionnent non seulement sur std::tuple, mais également sur les structures, std::pair et les tableaux (std::array ou tableau C-style).
Il est ainsi possible de récupérer les éléments d’une structure directement:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
struct foo { int i; double d; std::string s; }; foo a{42, 42., "42"}; auto [i, d, s] = a; std::cout << i << ", " << d << ", " << s << '\n'; std::array<float, 3> arr{ 3.0f, 2.0f, 1.0f }; auto [f1, f2, f3] = arr; std::cout << f1 << ", " << f2 << ", " << f3 << '\n'; int t[3] = {1, 2, 3}; auto [t1, t2, t3] = t; std::cout << t1 << ", " << t2 << ", " << t3 << '\n'; |
1 2 3 |
42, 42, 42 3, 2, 1 1, 2, 3 |
Les règles qui déterminent dans quels cas une structure peut profiter de la syntaxe des structured bindings sont détaillées dans la P0217R3 :. Un type est Destructurable à l’une des conditions suivantes :
- Toutes ses membres non statiques doivent être publics, ne pas être des unions anonymes et doivent être des membres direct du type ou d’une classe mère public du type.
- Le type a une fonction Type::get<> ou une version libre.
- Le type a une spécialisation de std::tuple_size<> ou de std::tuple_element<>.
L’avenir des structured bindings
La version actuelle des structured bindings pourrait être étendu. La P0144R0 avait proposé d’autres cas d’usage qui n’ont pas (encore) été acceptés, parmis lesquels :
- la possibilité d’ignorer une variable : auto [x, std::ignore_t, z] = f();
- le support de la récursivité, qui permettrait par exemple d’écrire quelque-chose comme :
1234std::map<std::string, std::tuple<int, char, double>> f();for (auto&& [name, [i, c, d]] : f())std::cout << name << ", " << i << ", " << c << ", " << d << '\n'; - la qualification précise des variables : auto [i, const d, const& s] = f();
- l’initialisation depuis une std::initializer_list ou une liste d’initialisation :
1auto [i, j, k] = {0, 1, 2};
On peut donc imaginer que certaines de ces fonctionnalités seront proposées par la suite, si le comité juge qu’elles ont leur place dans le langage.
Références
- P0144R0 [EN].
- P0217R3 [EN].
- Structured bindings – cppreference.com [EN]
- C++17 Structured bindings – Steve Lorimer – Github [EN]
You must log in to post a comment.