Zwei Werte, ein Objekt
Manchmal musst du zwei Dinge zusammenhalten: einen Namen und einen Punktestand, ein x und ein y, ein „Hat es geklappt?"-Flag und das Ergebnis. Du könntest für jede solche Gruppierung ein struct definieren, aber für Wegwerf-Gruppierungen ist das viel Zeremoniell. std::pair (aus <utility>) bündelt genau zwei Werte in einem Objekt, und std::tuple (aus <tuple>) verallgemeinert das auf eine beliebige feste Anzahl von Werten.
std::pair ist dir auf dem Weg hierher bereits indirekt begegnet: Jedes Element einer std::map ist ein pair<const Key, Value>. Diese Seite macht das explizit und zeigt die modernen, lesbaren Wege, diese Typen zu erzeugen und zu entpacken.
Die beiden Mitglieder heißen immer .first und .second – sie übernehmen nicht die Namen deiner Variablen. Das ist der Preis eines generischen Typs: Die Feldnamen sind positionsbezogen, nicht beschreibend.
Pairs erzeugen
Es gibt drei gängige Wege, ein pair zu erzeugen, und alle erzeugen dasselbe Objekt.
make_pair leitet die Elementtypen für dich ab, was vor C++17 praktisch war. Heute deckt die Initialisierung mit geschweiften Klammern plus Class Template Argument Deduction (pair p{"Boris", 85};) die meisten Fälle ab, aber du wirst make_pair in bestehendem Code immer noch überall sehen.
Ein Fallstrick bei der Ableitung: make_pair("hi", 3) leitet pair<const char*, int> ab, nicht pair<string, int>. String-Literale sind keine std::string. Wenn du ein string brauchst, sage es explizit – make_pair(string("hi"), 3) oder schreibe den Pair-Typ aus –, sonst bekommst du später möglicherweise überraschende Vergleiche oder Kopien.
Entpacken mit Structured Bindings
.first und .second überall zu lesen wird schnell unleserlich, weil die Namen dir nichts sagen. Mit Structured Bindings in C++17 kannst du den beiden Feldern in einer einzigen Zeile echte Namen geben:
Das glänzt in einer bereichsbasierten for-Schleife über eine map, bei der jedes Element ein pair ist. Statt it->first / it->second benennst du Schlüssel und Wert direkt:
Verwende in der Schleife const auto&, genau wie bei jedem Container-Element – es vermeidet das Kopieren jedes pair und signalisiert, dass du nur liest. Lässt du das & weg, kopierst du jeden Eintrag; das ist ein stiller Performance-Bug bei einer großen Map.
Wenn zwei nicht genug sind: tuple
Ein pair endet bei zwei Werten. Wenn du drei oder mehr brauchst, ist std::tuple dieselbe Idee mit einer beliebigen Anzahl. Du erzeugst es mit Initialisierung durch geschweifte Klammern oder mit make_tuple und liest es mit std::get<N>, wobei N ein zur Kompilierzeit bekannter Index ist.
Der Index in get<> muss eine zur Kompilierzeit bekannte Konstante sein. get<i>(record), wobei i eine Laufzeitvariable ist, lässt sich nicht kompilieren: Die Felder eines Tuples können unterschiedliche Typen haben, daher muss der Elementtyp während der Kompilierung aufgelöst werden, nicht zur Laufzeit. Wenn du dich dabei ertappst, einen Laufzeitindex zu wollen, brauchst du wahrscheinlich einen vector.
Structured Bindings funktionieren auch bei Tuples, und das ist die lesbare Art, eines zu verarbeiten:
Mehrere Werte zurückgeben
Der alltägliche Grund, zu diesen Typen zu greifen, ist die Rückgabe von mehr als einem Wert aus einer Funktion, ohne ein Struct zu erfinden oder mit Ausgabeparametern zu jonglieren. Bündle die Ergebnisse in ein pair oder tuple und entpacke sie an der Aufrufstelle.
Für drei oder mehr Ergebnisse gib auf dieselbe Weise ein tuple zurück. Es gibt außerdem std::tie, einen älteren Trick, der in bereits vorhandene Variablen entpackt, statt neue zu deklarieren – nützlich, wenn du ein Feld mit std::ignore ignorieren willst:
Wann man jedoch aufhören sollte: Wenn dieselbe Gruppe von Feldern an mehr als einer Stelle auftaucht oder du ständig vergisst, ob .second der Score oder die Anzahl ist, definiere ein struct mit benannten Mitgliedern. pair und tuple eignen sich am besten für lokale, kurzlebige Gruppierungen; benannte Felder gewinnen, sobald die Daten länger leben als ein einzelner Ausdruck.
Vergleichen und Sortieren
Ein praktischer Bonus: pair und tuple bringen eingebaute Vergleichsoperatoren mit, die lexikografisch arbeiten – sie vergleichen das erste Element und gehen erst dann zum nächsten über, wenn die ersten gleich sind. Das macht sie zu perfekten Sortierschlüsseln.
Beachte, dass die Reihenfolge der Felder entscheidend ist: age an erster Stelle sortiert primär nach Alter und dann nach Name als Tiebreaker. Wolltest du zuerst nach Name sortieren, würdest du die Elementreihenfolge des Tuples vertauschen. Genau dieser Standardvergleich ist der Grund, warum pair<priority, item> ein gängiges Idiom für Prioritätswarteschlangen ist.
Als Nächstes: Iteratoren
Du hast nun .first, .second, it->first und *it rund um Container auftauchen sehen – das, was ein pair-Element tatsächlich mit der map verbindet, in der es lebt, ist ein Iterator. Die nächste Seite erklärt Iteratoren richtig: was begin() und end() wirklich zurückgeben, wie ++it einen Container durchläuft, und die Fallen der Iterator-Invalidierung, die einige der übelsten Fälle von Undefined Behavior in C++ verursachen.
Häufig gestellte Fragen
Was ist der Unterschied zwischen pair und tuple in C++?
std::pair enthält genau zwei Werte, auf die man mit .first und .second zugreift. std::tuple enthält eine beliebige feste Anzahl von Werten (null, zwei, drei oder mehr), auf die man mit std::get<N>(t) zugreift. Ein pair ist im Grunde ein zweielementiges Tuple mit freundlicheren Mitgliedsnamen; greife nur dann zu tuple, wenn du drei oder mehr Felder brauchst.
Wie greift man in C++ auf Tuple-Elemente zu?
Verwende std::get<N>(t) mit einem zur Kompilierzeit bekannten Index, z. B. std::get<0>(t). Ab C++17 kannst du es auch mit Structured Bindings entpacken: auto [a, b, c] = t; gibt jedem Element eine eigene benannte Variable. Du kannst ein Tuple nicht mit einer Laufzeitvariable indizieren – std::get<i> erfordert, dass i eine Konstante ist.
Wie gibt man in C++ mehrere Werte aus einer Funktion zurück?
Gib ein std::pair oder std::tuple zurück und entpacke es an der Aufrufstelle mit Structured Bindings: auto [ok, value] = parse(text);. Das ist sauberer als Ausgabeparameter und vermeidet die Definition eines Wegwerf-Structs, auch wenn ein benanntes Struct lesbarer ist, wenn die Felder einen einzelnen Aufruf überdauern.