[:tr]Merhabalar arkadaşlar, yeni bir haftalık C++ yazısı ile birlikteyiz. Bu yazımızda da modern C++ 17 ile birlikte gelen bir diğer kabiliyet olan “Structured Binding”‘e bakacağız. Ben yazımda buna kısaca “yapısal bağlama” diyeceğim.
Bu kabiliyet bize ne kazandırıyor? Kısaca bu kabiliyet ile birlikte birden fazla değişkenin tek bir ifade ile “tuple”/”pair” ya da benzeri yapıları kullanarak ilklendirebileceğiz, daha önce std::tuple ve benzeri yapılarla dolaylı yoldan sağlanan çoklu değer dönme kabiliyetini de artık kullanabileceğiz. Bu kabiliyetin arkasında yatan motivasyonun detayları için kaynaklar kısmında verdiğim standart dokümanına bir göz atmanızda fayda var.
Buna benzer kabiliyetleri başka diller açıp atama (“unpacking“), çoklu atama (“multiple assignment“) isimleri ile sunuyorlar. Ör. Pyhton da (x, y) = 10, 20 diyerekten tek bir satırda ilgili değişkenleri atayabiliyorsunuz.
İşte bu kabiliyet artık C++ ile de sunuluyor.
Yazımda detaylı olarak bu kabiliyeti tek tek anlatmaktansa genel yapısından bahsedip daha önceki yazılarımda olduğu gibi çeşitli örnekler üzerinden giderek sizlere aktarmayı planlıyorum.
Genel yapı
Yapısal bağlama genel olarak aşağıdaki format ile özetlenebilir:
auto [element1, element2, ….] = { pair, tuple, struct ya da dizi tanımlamaları }
Dikkat edilecek hususlar:
- “element1, …, ” ‘de virgül ile ayrılan elemanların adeti ile atama operatörü sağındaki elemanların sayısı aynı olmalıdır,
- Atama operatörünün sağında ise aşağıdakilerden birisi olabilir:
- std:: pair ya da std::tuple
- bir struct nesnesi. Struct nesnesi içerisindeki üyelerin hiçbirisi statik olmamalı. Bunların tanımlanma sırasına göre atama operatörünün solundaki elemanlar doldurulacak
- Sabit boyutlu dizi
- Yukarıdaki durumlardan birisi sağlanmadığı durumda derleme hatası ile karşılaşırsınız
- Bu arada atamalar sırasında gereksiz kopyalamaları önlemek adına olabildiğince atama operatörünün solunda referans tiplerini kullanmaya özen göstermekte fayda var
- Operatörün solunda auto, auto&, const auto, const auto& ve auto&& kullanımları mümkün.
std::tuple ile kullanımı
Yapısal bağlama’nın ilk kullanım örneği tuple‘lar ile alaklı olacak. Özellikle “pair” (bayadır mevcut) ya da “tuple” (C++ 11 ile birlikte geldi) benzeri yapılar ile uğraşıyor iseniz bunların barındırdığı değerlere erişme ihtiyacı duymuşsunuzdur. std::pair aslında map konteynerinin temel taşı diyebiliriz. Std::tuple da std::pair‘in aslında bir anlamda genelleştirilmiş hali. Normal şartlarda tuple ile ifade edilen bir değişkene ilişkin elemanları farklı değişkenlere atamak (ya da açıp kullanmak) için önceden takip edilen yöntem std::tie() metoduydu. Yeni gelen bu kabiliyet ile birlikte “auto [var1, var2, …] = tuple;” kullanımı ile bu değişkenlere erişim sağlanıyor. Hemen buna ilişkin bir kullanıma bakalım:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | #include <iostream> #include <tuple> int main() { auto employee = std::make_tuple("Ahmet", "Can", 35, 174.3); // C++ 17 Oncesi // tuple icerisindeki degisklenlere erismek icin { // Oncelikle calisan verileri tutmak icin degiskenleri tanimlayalim std::string name; std::string surname; int age; double height; std::tie(name, surname, age, height) = employee; std::cout << "name = " << name << " surname = " << surname << " age = " << age << " height = " << height << "cm" << std::endl; } // C++ 17 Sonrasi kullanim { auto [ name, surname, age, height ] = employee; std::cout << "name = " << name << " surname = " << surname << " age = " << age << " height = " << height << "cm" << std::endl; } return 0; } |
tuple kullanımındaki bir diğer sıkıntı ise std::tie() ile tuple‘daki elemanların referansını almak mümkün değil idi. Bunun için tek yol “std::get<2>(employee)” metodu. Yeni mekanizma ile artık bu da mümkün:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | #include <iostream> #include <tuple> int main() { auto employee = std::make_tuple("Ahmet", "Can", 35, 174.3); // C++ 17 Oncesi // tuple icerisindeki degiskenlerin referanslarini almak icin { // Oncelikle calisan verileri tutmak icin degiskenleri tanimlayalim auto& name = std::get<0>(employee); auto& surname = std::get<1>(employee); auto& age = std::get<2>(employee); auto& height = std::get<2>(employee); std::cout << "name = " << name << " surname = " << surname << " age = " << age << " height = " << height << "cm" << std::endl; // Yasi arttıralim age++; std::cout << "yeni yas = " << std::get<2>(employee) << std::endl; } // C++ 17 Sonrasi kullanim { auto& [ name, surname, age, height ] = tuple; std::cout << "name = " << name << " surname = " << surname << " age = " << age << " height = " << height << "cm" << std::endl; // Yasi arttıralim age++; std::cout << "yeni yas = " << std::get<2>(employee) << std::endl; } return 0; } |
Buradaki kullanımların benzeri std::pair ve std::array için de geçerlidir.
struct ile kullanımı
std::tuple‘a benzer şekilde struct nesnelerinin de her bir elemanına bu mekanizma ile tek bir seferde ulaşabiliyoruz.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | #include <iostream> #include <string> #include <tuple> struct Employee { std::string name; std::string surname; int age; double height; }; int main() { Employee ahmet { "Ahmet", "Can", 35, 174.3 }; // Oncelikle calisan verileri tutmak icin degiskenleri tanimlayalim std::string name; std::string surname; int age; double height; // degerleri alalim auto [ name, surname, age, height ] = ahmet; std::cout << "name = " << name << " surname = " << surname << " age = " << age << " height = " << height << "cm" << std::endl; return 0; } |
Bu kullanımların benzer şekilde struct dönen metotlarda da kullanabilirsiniz.
Diziler ile kullanımı
Yukarıda anlatılan kullanımlar ile birlikte yapısal bağlamaya geleneksel diziler ile de kullanabilirsiniz. Aşağıda bu kullanıma ilişki örneği görebilirsiniz.
Burada önemli olan ilgili dizinin boyutunun belli olması.
1 2 3 4 5 6 7 | int arrayExample[] = { 47, 11 }; // x ve y arrayExample elemanları ile doldurulur auto [x, y] = arr; // Derleme hatası boyutlar farklı auto [z] = arr; |
move semantiği ile kullanımı
Yapısal bağlama yukarıda bahsettiğim gibi move semantiği ile de kullanılabilmektedir. Aşağıda bu kullanıma ilişkin örnek kodu görebilirsiniz:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | Employee ms = { "Ahmet", "Can", 35, 174.5 }; // İlgili ms nesnesine rvalue referans auto&& [name, surname, age, height] = std::move(ms); // "Ahmet Can" basılır std::cout << "ms.name: " << ms.surname << '\n'; // İsmi de tasiyalim std::string newName = std::move(name); // Degeri belli olmayan bir sey basilir (cunku tasindi yukarıda) std::cout << "ms.surname: " << ms.surname << std::endl; // Benzer sekilde degeri belli olmayan bir sey basilir (cunku tasindi yukarıda) std::cout << "name: " << name << std::endl; // Ali basilir std::cout << "New name: " << newName << std::endl; |
std::map konteyneri ile kullanımı
Bu mekanizmanın bir diğer kullanımı ise std::map konteynerinin elemanlarının üzerinde gezinme.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | #include <iostream> #include <string> #include <unordered_map> template<typename K, typename V> void printMap(std::unordered_map<K, V> const &m) { for (auto const& pair: m) { std::cout << "{" << pair.first << ": " << pair.second << "}\n"; } } int main() { std::unordered_map<int, std::string> m = { {1, "Begum"}, {8, "Ahmet"}, {3, "Ayse"} }; // C++17 Oncesi for (auto const& pair: m) { std::cout << "{" << pair.first << ": " << pair.second << "}\n"; } // C++17 Sonrası // Daha anlasilir kullanim for (auto&& [key, value]: m) { std::cout << "{" << key << ": " << value << "}\n"; } return 0; } |
Kaynaklar
- https://wg21.link/p0217r3
- http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0144r0.pdf
- https://skebanga.github.io/structured-bindings/
- https://www.bfilipek.com/2017/07/cpp17-details-simplifications.html#structured-binding-declarations
[:en]Hello everybody, we are together again with another weekly C++ post. In this post, we are going to take a look at a feature that provided with C++ 17, “Structured Binding”‘. So what does this feature provide us? Briefly, this feature let you assign or initialize multiple variables with tuple/pairor similar structs with one statement. It is now also possible to return multiple variables which were possible only via std::tuple like constructs. You may check out the references for more motivation behind this feature.
Similar capabilites are provided as Unpacking, Multiple Assignment in other programming languages. For instance, in Python you can assign two variables with a single (x, y) = 10, 20statement. And now this is also possible with C++.
In my post, instead of going through all details of this capability, I will try to give you some important usages of this capability with sample codes.
General syntax:
Structured binding can be summarized with following syntax:
auto [element1, element2, ….] = { pair, tuple, struct or array }
Points to consider:
- “element1, …, ” the number of elements seperated with comma should be same as the ones provided at the right side of assignment operator,
- The element at the right side of assignment can be one of following:
- std:: pair or std::tuple
- A struct instance. All of the struct members should be non-static. The order of definition is important,
- Fixed size arrays,
- You may encounter with compilation error if one of above conditions is not met,
- To prevent any unnecessary copy operation, reference types should be used at the left side of assignment operator as much as possible,
- auto, auto&, const auto, const auto& and auto&& can be used at the left side of assignment operator.
Usage with std::tuple:
The first usage of structured binding will be about tuple’s. If you are using “std::pair” or “std::tuple” then you probably required to access the individual data. In fact, std::pair is an important part of std::map. Std::tuple is a more generalized version of std::pair. Traditionally (before C++ 17), the approach to access the individual elements of tuple and assign them to other variables is perfomed through std::tie() method. With structured binding, it is possible to use “auto [var1, var2, …] = tuple;” to access corresponding variables. Let us look at an example usage:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | #include <iostream> #include <tuple> int main() { auto employee = std::make_tuple("Ahmet", "Can", 35, 174.3); // Pre-C++ 17 // To access the tuple elements { // Fırst define variables std::string name; std::string surname; int age; double height; std::tie(name, surname, age, height) = employee; std::cout << "name = " << name << " surname = " << surname << " age = " << age << " height = " << height << "cm" << std::endl; } // After C++ 17 { auto [ name, surname, age, height ] = employee; std::cout << "name = " << name << " surname = " << surname << " age = " << age << " height = " << height << "cm" << std::endl; } return 0; } |
Another issue with tuple usage is that it is not possible to get reference of elements with std::tie() method. Only way to do so is to use “std::get<2>(employee)” method. With this capability, this is also possible:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | #include <iostream> #include <tuple> int main() { auto employee = std::make_tuple("Ahmet", "Can", 35, 174.3); // Before C++ 17 // To obtain the references of tuple elements { // First of all define variables auto& name = std::get<0>(employee); auto& surname = std::get<1>(employee); auto& age = std::get<2>(employee); auto& height = std::get<2>(employee); std::cout << "name = " << name << " surname = " << surname << " age = " << age << " height = " << height << "cm" << std::endl; // Incremenet age age++; std::cout << "yeni yas = " << std::get<2>(employee) << std::endl; } // After C++ 17 { auto& [ name, surname, age, height ] = tuple; std::cout << "name = " << name << " surname = " << surname << " age = " << age << " height = " << height << "cm" << std::endl; // Increment age age++; std::cout << "yeni yas = " << std::get<2>(employee) << std::endl; } return 0; } |
Same usage is also possible for std::pair and std::array.
Usage with struct:
We can also access and assign each member of struct objects similar to std::tuple.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | #include <iostream> #include <string> #include <tuple> struct Employee { std::string name; std::string surname; int age; double height; }; int main() { Employee ahmet { "Ahmet", "Can", 35, 174.3 }; // Let us define variables to hold employee data std::string name; std::string surname; int age; double height; // obtain values auto [ name, surname, age, height ] = ahmet; std::cout << "name = " << name << " surname = " << surname << " age = " << age << " height = " << height << "cm" << std::endl; return 0; } |
This can also be used with functions that return struct.
Usage with arrays:
The structured binding can also be used with traditional arrays. A sample code for this usage is provided below. It should be noted that the size of array should be matched.
1 2 3 4 5 6 7 | int arrayExample[] = { 47, 11 }; // x and y is filled with arrayExample elements auto [x, y] = arr; // Compilation error. Sizes do not match auto [z] = arr; |
Usage with move semantics:
As I mentioned above structured binding can also be used with move semantics. You can find a sample code that illustrate this usage:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | Employee ms = { "Ahmet", "Can", 35, 174.5 }; // rvalue reference to ms object auto&& [name, surname, age, height] = std::move(ms); // "Ahmet Can" is printed std::cout << "ms.name: " << ms.surname << '\n'; // Move name std::string newName = std::move(name); // Invalid and nondetermined value is printed (we already moved value) std::cout << "ms.surname: " << ms.surname << std::endl; // Invalid and nondetermined value is also printed (we already moved value) std::cout << "name: " << name << std::endl; // Ali is printed std::cout << "New name: " << newName << std::endl; |
Usage with std::map:
You can also use this mechanism with std::map container to traverse elements as illustrated below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | #include <iostream> #include <string> #include <unordered_map> template<typename K, typename V> void printMap(std::unordered_map<K, V> const &m) { for (auto const& pair: m) { std::cout << "{" << pair.first << ": " << pair.second << "}\n"; } } int main() { std::unordered_map<int, std::string> m = { {1, "Begum"}, {8, "Ahmet"}, {3, "Ayse"} }; // Before C++17 for (auto const& pair: m) { std::cout << "{" << pair.first << ": " << pair.second << "}\n"; } // After C++17 // More clear and simple usage for (auto&& [key, value]: m) { std::cout << "{" << key << ": " << value << "}\n"; } return 0; } |
References:
- https://wg21.link/p0217r3
- http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0144r0.pdf
- https://skebanga.github.io/structured-bindings/
- https://www.bfilipek.com/2017/07/cpp17-details-simplifications.html#structured-binding-declarations
[:]
