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.
İçerik
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; } |