Permalänk
Medlem

C++11 - find_if

Jag har börjat utforska lite C++11 specifika funktioner. Något jag hittade var find_if, vilken använder lamba notation. Är det någon som är villig att förklara för mig hur t.ex detta skulle kunna skrivas med find_if istället?:

void Parser::findGUID(std::string alias, int *guid){ for(::std::vector<User>::iterator i = _users->begin(); i != _users->end(); ++i){ if(i->alias == alias) *guid = i->GUID; } }

Jag har gjort några försök, men har inte riktigt lyckats...
_users är en vektorpekare om någon inte förstod det.

Visa signatur
Permalänk
Datavetare

Till att börja med kan man förenkla din for-loop med C++11 specifika finesser, den kan skrivas så här

void Parser::findGUID(std::string alias, int *guid) { for(auto &user: *_users) { if(user.alias == alias) *guid = user.GUID; } }

find_if kan t.ex. användas på detta sätt

void Parser::findGUID(std::string alias, int *guid) { auto match = std::find_if(begin(*_users), end(*_users), [&](const User &user) { return user == alias; }); if (match != end(*_users)) { *guid = match->GUID; } }

Visa signatur

Care About Your Craft: Why spend your life developing software unless you care about doing it well? - The Pragmatic Programmer

Permalänk
Medlem
Skrivet av Yoshman:

Till att börja med kan man förenkla din for-loop med C++11 specifika finesser, den kan skrivas så här

void Parser::findGUID(std::string alias, int *guid) { for(auto &user: *_users) { if(user.alias == alias) *guid = user.GUID; } }

find_if kan t.ex. användas på detta sätt

void Parser::findGUID(std::string alias, int *guid) { auto match = std::find_if(begin(*_users), end(*_users), [&](const User &user) { return user == alias; }); if (match != end(*_users)) { *guid = match->GUID; } }

Okej, så man kan faktiskt börja använta "auto" nu. Tack

Visa signatur
Permalänk
Datavetare

Man har ändrat betydelsen av auto i C++11, då nyckelordet fanns redan i ursprungliga C. I C och C++ innan C++11 var det ett rätt meningslöst nyckelord som betyder en variabel som använder auto som attribut säger att utrymmet variabeln är tilldelat är bara garanterat giltigt så länge det kontext (i.e. stacken till nuvarande funktion) variabel är definierad är aktivt.

auto och static är i någon mån motsatser för variabler, finns däremot ingen anledning att använda auto då alla variabler implicit har det attributet om man inte använder static.

I C++11 ändrade man därför betydelsen av auto till att betyda: "den typ som kompilatorn anser att variabeln måste ha för att passa uttrycket till höger", d.v.s. samma betydelse som var i t.ex. C# och Scala. Man kunde inte använda var då det redan finns så extremt mycket existerande C++ kod och var används ibland som namn på saker.

Notera också att begin() och end() numera är fristående funktioner i stället för att vara medlemsfunktioner (det du skrev fungerar också då C++11 är bakåtkompatibelt). Fördelen med att inte ha dessa som medlemsfunktioner är att C++ har extremt avancerade mekanismer för typspecificering och typgeneralisering via templates, om man i stället använder detta för att skapa iteratorer är det fullt möjligt att iterera över vilken typ som helst, inte bara saker som är klasser.

En fördel då i C++11 är att detta fungerar:

int nums[] = { 1, 1, 2, 3, 5, 7 }; for (auto it = std::begin(nums); it != std::end(nums); ++it) { std::cout << *it << std::endl; } // vilket då också betyder att man kan göra detta for (auto num: nums) { std::cout << num << std::endl; }

Däremot så är det rekommenderade sättet att definiera statiska arrayer inte C-syntax utan man bör göra använda std::array i C++11

std::array<int, 6> nums{ {1, 1, 2, 3, 5, 7} };

nums nu "vet" sin egen storlek och har en del andra saker man ofta vill ha på arrayer.

Visa signatur

Care About Your Craft: Why spend your life developing software unless you care about doing it well? - The Pragmatic Programmer

Permalänk
Medlem

@Yoshman
Tack, mycket bra information! Verkar som det finns en del väldigt användbara grejer i C++11.

Visa signatur
Permalänk
Medlem
Skrivet av Yoshman:

int nums[] = { 1, 1, 2, 3, 5, 7 }; for (auto it = std::begin(nums); it != std::end(nums); ++it) { std::cout << *it << std::endl; } // vilket då också betyder att man kan göra detta for (auto num: nums) { std::cout << num << std::endl; }

Detta är galet coolt, och nytt för mig. Hur funkar detta, håller kompilatorn koll på längden eller nåt sånt? Vad händer om man har en dynamiskt allokerad array?

Visa signatur

Louqe Ghost S1 MK3 | Asus ROG Strix B660-I Gaming WiFi | Intel Core i7 12700K | nVidia RTX 2070 Super FE | Corsair 64GB (2x32GB) DDR5 5600MHz CL40 Vengeance | Samsung 980 PRO M.2 NVMe SSD 2TB | Corsair SF750 750W 80+ Platinum | Noctua NH-L12 Ghost S1 edition | Kablar från pslate customs | 2 stk Dell Ultrasharp 3014 | Logitech MX Keys | Logitech MX Anywhere

Permalänk
Medlem

@sunefred: Om du har en dynamiskt allokerad c-array går det inte att använda den i en s.k. range based for-loop då kompilatorn inte vet hur stor den är.

Detta är dock inte ett problem då man "aldrig" (om man absolut inte måste) ska använda c-arrayer i nyare versioner av C++ (man använder istället std::array eller annan lämplig container).

Permalänk
Datavetare

@sunefred: faktum är att man skulle kunna göra något som är väldigt likt den for-loop med iteratorn även i C

#include <stdio.h> typedef int * int_iterator; #define begin(arr) (arr) #define end(arr) ((arr) + sizeof (arr) / sizeof (*arr)) int main() { int nums[] = { 1, 1, 2, 3, 5, 7 }; for (int_iterator it = begin(nums); it != end(nums); ++it) { printf("%d\n", *it); } }

Men det är faktiskt inte så det fungerar i C++11 då STL bygger på överlagring av begin()/end() beroende på typ man skickar in. När man ändrade begin()/end() från att vara medlemsfunktioner på någon form av klass till att i stället använda C++ brutalt kraftfulla template mekanism öppnade sig en rad nya möjligheter.

Vi vill alltså att begin()/end() fungerar ungefär som i C-varianten ovan, men det måste vara en lösning som inte bygger på preprocessorn (den saknar de möjligheter till överlagring vi behöver).

Lösningen kommer behöva template då vi vill göra en specialisering för just C-arrayer. Vidare behöver vi kunna hantera arrayer av olika typer och av olika storlek, låter väldigt mycket som det då behövs två template argument, typen på elementen i arrayen och längden på arrayen. begin() ska returnera första elementet och end() ska returnera adressen till en efter sista elementet

template<typename ElemType, size_t ArrayLen> ElemType * begin(...) { ... }

Det luriga nu är vad ska man använda som argument? Tja, vad är typen på en array givet de template argument som valdes här? Det ska vara array med element av typen ElemType av storlek ArrayLen. Vi vill också ta en referens till denna array, inte göra en kopia så måste få in & operatorn

template<typename ElemType, size_t ArrayLen> ElemType * begin(ElemType (&anArray)[ArrayLen]) { return anArray; } template<typename ElemType, size_t ArrayLen> ElemType * end(ElemType (&anArray)[ArrayLen]) { return anArray + ArrayLen; }

Detta fungerar när det används så här

int nums[] = { 1, 1, 2, 3, 5, 7 }; for (auto it = begin(nums); it != end(nums); ++it) { std::cout << *it << std::endl; }

därför att enda möjliga funktionen för begin()/end() är de vi definierat ovan. Typen på vår array är int[6], vilket betyder att enda sättet kompilatorn kan få ihop det är genom att sätta ElemType till int och ArrayLen till 6.

Template i C++ är komplicerat, men det riktigt värdefulla är att man inte alls behöver förstå detaljerna i hur templates fungerar för att kunna använda dem. Man måste däremot ha örnkoll på detaljerna om man ger sig på att skriva egna templates.

Visa signatur

Care About Your Craft: Why spend your life developing software unless you care about doing it well? - The Pragmatic Programmer