# Iteratorer och STL Ranges

30 november 2025 | https://www.ribomation.se/blog/2025/iteratorer-och-stl-ranges/

C++20 gav oss ranges som gör STL‑algoritmer både enklare och mer uttrycksfulla; skicka hela containern i stället för (begin, end), kedja transformerande views som en shell‑pipeline och stoppa iterationer med smarta sentinels. I artikeln visar jag hur egenbyggda containrar och iteratorer kan anpassas till ranges och views, inklusive transform, projektioner via medlemsfunktionspekare och hur pipelinen drivs.

I de olika kodsnuttarna nedan skapar jag en enkel-länkad lista med tal, kvadrerar dessa med en lambda och funktionen `transform`, samt lägger resultatet i en vector, som sedan skrivs ut. Detta gör jag för att illustrera de olika nyheterna i det som kallas _ranges & views_.

`std::transform`
----------------

Före C++20, när man vill applicera en STL funktion på elementen i en container, så anropade man den med ett intervall (begin, end).

```cpp
auto sq     = [](auto x) { return x * x; };
auto input  = SingleLinkedList{1, 2, 3, 4, 5};
auto output = std::vector<int>{}; output.resize(input.size());
std::transform(input.begin(), input.end(), output.begin(), sq);
for (auto&& x: output) cout << x << " "; //1 4 9 16 25
```

`std::ranges::transform`
------------------------

Med C++20, fick vi nya versioner av de flesta STL funktioner, i namespace std::ranges. Dels, kan man precis som tidigare skicka ett intervall. Men man kan också skicka med hela container objektet, som exemplet nedan visar.

```cpp
auto input  = SingleLinkedList{1, 2, 3, 4, 5};
auto output = std::vector<int>{}; output.resize(input.size());
std::ranges::transform(input, output.begin(), sq);
for (auto&& x: output) cout << x << " "; //1 4 9 16 25
```

Sentinel Object
---------------

När man använder ett interval som input, kan man numera också skicka med en speciell slutmarkör, kallat _sentinel object_.

```cpp
template<typename Iter, typename Value>
struct StopAfterValue {
    Iter   end;
    Value  cutoff;
    friend bool operator==(Iter it, StopAfterValue s) {
        return it == s.end || *it > s.cutoff;
    }
    friend bool operator==(StopAfterValue s, Iter it) { return it == s; }
    friend bool operator!=(Iter it, StopAfterValue s) { return !(it == s); }
    friend bool operator!=(StopAfterValue s, Iter it) { return !(it == s); }
};
//...
auto input  = SingleLinkedList{1, 2, 3, 4, 5, 6, 7, 8, 9};
auto output = std::vector<int>{}; output.resize(5);
auto stop   = StopAfterValue{.end = input.end(), .cutoff = 5};
std::ranges::transform(input.begin(), stop, output.begin(), sq);
for (auto&& x: output) cout << x << " "; //1 4 9 16 25
```

View-Pipeline
-------------

Utöver `std::ranges` har vi också ett namespace kallat `std::views` (_alias till_ `std::ranges::views`). Här finns så kallade _view-classes_ och _view-factory-functions_. Tankegången är att associera en container med en eller flera view objects. Riktigt spännande blir det när man skapar en pipeline via den överlagrade | operatorn, vilket får koden att likna _shell pipes_.

```cpp
auto input  = SingleLinkedList{1, 2, 3, 4, 5};
auto output = input | std::views::transform(sq) | std::ranges::to<std::vector<int>>();
for (auto&& x: output) cout << x << " "; //1 4 9 16 25
```

> Observera att `std::ranges::to` formellt tillhör C++23

Det sista exemplet kan förkortas genom att ta bort destinationen (sista delen) och låta for-each loop:en driva pipelinen.

```cpp
auto input = SingleLinkedList{1, 2, 3, 4, 5};
for (auto&& x: input | std::views::transform(sq)) cout << x << " "; //1 4 9 16 25
```

En _view-pipeline_ är en så kallad _pull-pipeline_, vilket innebär att sista delen måste vara drivande för att data ska börja flöda. En konsekvens är att det går att sätta samman en pipeline, utan drivande komponent i flera steg, och sen sist på lägga till en driver, såsom en loop eller ett destinationsobjekt.

> Notera att `std::views::transform` inte skapar en ny container – elementen beräknas ‘on demand’ när loopen ber om nästa element.

Projection (member function pointer)
------------------------------------

När man har en container med objekt, går det att extrahera delar av ett objekt med en så kallad projektion, vilket innebär att man skickar med en pekare till en medlemsfunktion, som blir anropad för att extrahera ett visst värde från respektive objekt. I exemplet nedan har vi en lista med Account-objekt och kvadrerar deras saldon.

```cpp
struct Account {
    string accno{};
    double balance{};
    Account(string accno, double balance)
        : accno(std::move(accno)), balance(balance) {}
    auto get_accno()   const { return accno; }
    auto get_balance() const { return balance; }
};
//...
auto input = SingleLinkedList<Account>{
    {"AAA"s, 10}, {"BBB"s, 20}, {"CCC"s, 30}, {"DDD"s, 40}, {"EEE"s, 50}
};
auto output = input
              | std::views::transform(&Account::get_balance)
              | std::views::transform(sq)
              | std::ranges::to<std::vector<double>>();
for (auto&& x: output) cout << x << " "; //100 400 900 1600 2500
```

> I standardalgoritmerna finns en särskild ‘projection’-parameter; här gör vi i stället motsvarande sak i två steg med `views::transform`.

Sammanfattning
--------------

*   C++20 Ranges låter dig köra STL-algoritmer mer uttrycksfullt: skicka hela containern istället för (begin, end), och kedja transformationer via views med | som en shell-pipeline.
*   Nya std::ranges-varianter av algoritmer (t.ex. ranges::transform) fungerar både med intervall och hela containrar, och kan stoppas med sentinels som markerar ett dynamiskt slut.
*   View-pipelines är “pull”-drivna: inget händer förrän sista steget konsumerar data (t.ex. for‑each eller to).
*   Projektioner gör det lätt att extrahera fält via medlemsfunktionspekare innan vidare transformation, t.ex. hämta saldo och kvadrera.
*   Egenbyggda containrar/iteratorer kan anpassas till ranges och views, så länge de exponerar rätt gränssnitt (begin/end, iterator-beteende) och uppfyller konceptkraven.

_Kort sagt_: ranges + views förenklar API:er, gör pipelines läsbara och flexibla, och spelar väl med både standardcontainrar och egna datatyper.
