Namensräume
Varianten
Aktionen

Operatorüberladung

Von cppreference.com
< cpp‎ | Sprache
 
 
C++ Sprache
Allgemeine Themen
Kontrollfluss
Bedingte Ausführungsaussagen
if
Iterationsanweisungen (Schleifen)
for
Bereichs-for (C++11)
Sprunganweisungen
Funktionen
Funktionsdeklaration
Lambda-Funktionsausdruck
inline-Spezifizierer
Dynamische Ausnahmespezifikationen (bis C++17*)
noexcept-Spezifizierer (C++11)
Ausnahmen
Namensräume
Typen
Spezifizierer
const/volatile
decltype (C++11)
auto (C++11)
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Speicherdauer-Spezifizierer
Initialisierung
Ausdrücke
Operatoren
Operatorrangfolge
Alternative Darstellungen
Literale
Boolesch - Ganzzahl - Gleitkommazahl
Zeichen - String - nullptr (C++11)
Benutzerdefinierte (C++11)
Dienstprogramme
Attribute (C++11)
Typen
typedef-Deklaration
Typalias-Deklaration (C++11)
Umwandlungen
Speicherzuweisung
Klassen
Klassenspezifische Funktionseigenschaften
explicit (C++11)
static

Spezielle Member-Funktionen
Templates
Sonstiges
 
 

Passt die C++ Operatoren für Operanden von benutzerdefinierten Typen an.

Inhalt

[edit] Syntax

Operatorfunktionen sind Funktionen mit speziellen Funktionsnamen

operator op (1)
operator new
operator new []
(2)
operator delete
operator delete []
(3)
operator co_await (4) (seit C++20)
op - einer der folgenden Operatoren:+ - * / % ^ & | ~ ! = < > += -= *= /= %= ^= &= |= << >> >>= <<= == != <= >= <=>(seit C++20) && || ++ -- , ->* -> () []
1) Ein überladener Satzzeichen-Operator.
4) Ein überladener co_await-Operator zur Verwendung in co_await-Ausdrücken.

Das Verhalten von Nicht-Satzzeichen-Operatoren wird auf ihren eigenen Seiten beschrieben. Sofern nicht anders angegeben, gilt die übrige Beschreibung auf dieser Seite nicht für diese Funktionen.

[edit] Erklärung

Wenn ein Operator in einem Ausdruck erscheint und mindestens einer seiner Operanden einen Klassentyp oder einen Aufzählungstyp hat, dann wird die Überladungsauflösung verwendet, um die benutzerdefinierte Funktion zu bestimmen, die unter allen Funktionen aufgerufen wird, deren Signaturen den folgenden entsprechen

Ausdruck Als Memberfunktion Als Nicht-Memberfunktion Beispiel
@a (a).operator@ ( ) operator@ (a) !std::cin ruft std::cin.operator!() auf
a@b (a).operator@ (b) operator@ (a, b) std::cout << 42 ruft std::cout.operator<<(42) auf
a=b (a).operator= (b) kann kein Nicht-Member sein Gegeben std::string s;, ruft s = "abc"; s.operator=("abc") auf
a(b...) (a).operator()(b...) kann kein Nicht-Member sein Gegeben std::random_device r;, ruft auto n = r(); r.operator()() auf
a[b...] (a).operator[](b...) kann kein Nicht-Member sein Gegeben std::map<int, int> m;, ruft m[1] = 2; m.operator[](1) auf
a-> (a).operator->( ) kann kein Nicht-Member sein Gegeben std::unique_ptr<S> p;, ruft p->bar() p.operator->() auf
a@ (a).operator@ (0) operator@ (a, 0) Gegeben std::vector<int>::iterator i;, ruft i++ i.operator++(0) auf

In dieser Tabelle ist @ ein Platzhalter, der alle übereinstimmenden Operatoren darstellt: alle Präfixoperatoren in @a, alle Postfixoperatoren außer -> in a@, alle Infixoperatoren außer = in a@b.

Zusätzlich werden für Vergleichsoperatoren ==, !=, <, >, <=, >=, <=>, bei der Überladungsauflösung auch die umgeschriebenen Kandidaten operator== oder operator<=> berücksichtigt.

(seit C++20)

Überladene Operatoren (aber nicht die eingebauten Operatoren) können in Funktionsschreibweise aufgerufen werden

std::string str = "Hello, ";
str.operator+=("world");                      // same as str += "world";
operator<<(operator<<(std::cout, str), '\n'); // same as std::cout << str << '\n';
                                              // (since C++17) except for sequencing

Statisch überladene Operatoren

Überladene Operatoren, die Memberfunktionen sind, können als statisch deklariert werden. Dies ist jedoch nur für operator() und operator[] zulässig.

Solche Operatoren können in Funktionsschreibweise aufgerufen werden. Wenn diese Operatoren jedoch in Ausdrücken vorkommen, benötigen sie immer noch ein Objekt vom Klassentyp.

struct SwapThem
{
    template<typename T>
    static void operator()(T& lhs, T& rhs) 
    {
        std::ranges::swap(lhs, rhs);
    }
 
    template<typename T>
    static void operator[](T& lhs, T& rhs)
    {
        std::ranges::swap(lhs, rhs);
    } 
};
inline constexpr SwapThem swap_them{};
 
void foo()
{
    int a = 1, b = 2;
 
    swap_them(a, b); // OK
    swap_them[a, b]; // OK
 
    SwapThem{}(a, b); // OK
    SwapThem{}[a, b]; // OK
 
    SwapThem::operator()(a, b); // OK
    SwapThem::operator[](a, b); // OK
 
    SwapThem(a, b); // error, invalid construction
    SwapThem[a, b]; // error
}
(seit C++23)

[edit] Beschränkungen

  • Eine Operatorfunktion muss mindestens einen Funktionsparameter oder einen impliziten Objektparameter haben, dessen Typ eine Klasse, eine Referenz auf eine Klasse, eine Aufzählung oder eine Referenz auf eine Aufzählung ist.
  • Die Operatoren :: (Gültigkeitsbereichsauflösung), . (Mitgliedszugriff), .* (Mitgliedszugriff über Zeiger auf Mitglied) und ?: (ternärer Bedingungsoperator) können nicht überladen werden.
  • Neue Operatoren wie **, <> oder &| können nicht erstellt werden.
  • Es ist nicht möglich, die Priorität, Gruppierung oder Anzahl der Operanden von Operatoren zu ändern.
  • Die Überladung von operator -> muss entweder einen Rohzeiger zurückgeben oder ein Objekt (per Referenz oder per Wert) zurückgeben, für das operator -> seinerseits überladen ist.
  • Die Überladungen der Operatoren && und || verlieren die Kurzschlussauswertung.
  • &&, || und , verlieren ihre speziellen Sequenzierungseigenschaften, wenn sie überladen werden, und verhalten sich wie reguläre Funktionsaufrufe, auch wenn sie ohne Funktionsaufrufschreibweise verwendet werden.
(bis C++17)

[edit] Kanonische Implementierungen

Abgesehen von den oben genannten Einschränkungen macht die Sprache keine weiteren Vorgaben, was die überladenen Operatoren tun oder welchen Rückgabetyp sie haben (dieser nimmt nicht an der Überladungsauflösung teil). Im Allgemeinen wird jedoch erwartet, dass überladene Operatoren sich so ähnlich wie möglich wie die eingebauten Operatoren verhalten: operator+ soll seine Argumente addieren und nicht multiplizieren, operator= soll zuweisen usw. Verwandte Operatoren sollen sich ähnlich verhalten (operator+ und operator+= führen dieselbe additionsähnliche Operation durch). Die Rückgabetypen sind durch die Ausdrücke begrenzt, in denen der Operator verwendet werden soll: Zuweisungsoperatoren geben beispielsweise per Referenz zurück, um das Schreiben von a = b = c = d zu ermöglichen, da die eingebauten Operatoren dies zulassen.

Häufig überladene Operatoren haben die folgenden typischen, kanonischen Formen:[1]

[edit] Zuweisungsoperator

Der Zuweisungsoperator operator= hat spezielle Eigenschaften: siehe Kopierzuweisung und Verschiebungszuweisung für Details.

Der kanonische Kopierzuweisungsoperator soll sicher bei Selbstzuweisung sein und die linke Seite (lhs) per Referenz zurückgeben

// copy assignment
T& operator=(const T& other)
{
    // Guard self assignment
    if (this == &other)
        return *this;
 
    // assume *this manages a reusable resource, such as a heap-allocated buffer mArray
    if (size != other.size)           // resource in *this cannot be reused
    {
        temp = new int[other.size];   // allocate resource, if throws, do nothing
        delete[] mArray;              // release resource in *this
        mArray = temp;
        size = other.size;
    }
 
    std::copy(other.mArray, other.mArray + other.size, mArray);
    return *this;
}

Die kanonische Verschiebungzuweisung soll das verschobene Objekt in einem gültigen Zustand hinterlassen (d. h. einen Zustand mit intakten Klasseninvarianten) und entweder nichts tun oder zumindest das Objekt bei Selbstzuweisung in einem gültigen Zustand hinterlassen und die linke Seite (lhs) per Referenz auf nicht-const zurückgeben und noexcept sein

// move assignment
T& operator=(T&& other) noexcept
{
    // Guard self assignment
    if (this == &other)
        return *this; // delete[]/size=0 would also be ok
 
    delete[] mArray;                               // release resource in *this
    mArray = std::exchange(other.mArray, nullptr); // leave other in valid state
    size = std::exchange(other.size, 0);
    return *this;
}
(seit C++11)

In Situationen, in denen die Kopierzuweisung nicht von der Wiederverwendung von Ressourcen profitieren kann (sie verwaltet kein heap-allokiertes Array und hat kein (möglicherweise transitives) Mitglied, das dies tut, wie z. B. ein Mitglied std::vector oder std::string), gibt es eine beliebte praktische Abkürzung: den Kopier-und-Tausch-Zuweisungsoperator, der seinen Parameter per Wert entgegennimmt (und somit sowohl als Kopier- als auch als Verschiebungzuweisung fungiert, abhängig von der Wertkategorie des Arguments), mit dem Parameter tauscht und den Destruktor die Bereinigung übernehmen lässt.

// copy assignment (copy-and-swap idiom)
T& T::operator=(T other) noexcept // call copy or move constructor to construct other
{
    std::swap(size, other.size); // exchange resources between *this and other
    std::swap(mArray, other.mArray);
    return *this;
} // destructor of other is called to release the resources formerly managed by *this

Diese Form bietet automatisch eine starke Ausnahmegarantie, verbietet aber die Wiederverwendung von Ressourcen.

[edit] Stream-Extraktion und -Einfügung

Die Überladungen von operator>> und operator<<, die einen std::istream& oder std::ostream& als linkes Argument nehmen, werden als Einfügungs- und Extraktionsoperatoren bezeichnet. Da sie den benutzerdefinierten Typ als rechtes Argument (b in a @ b) nehmen, müssen sie als Nicht-Member implementiert werden.

std::ostream& operator<<(std::ostream& os, const T& obj)
{
    // write obj to stream
    return os;
}
 
std::istream& operator>>(std::istream& is, T& obj)
{
    // read obj from stream
    if (/* T could not be constructed */)
        is.setstate(std::ios::failbit);
    return is;
}

Diese Operatoren werden manchmal als friend-Funktionen implementiert.

[edit] Funktionsaufrufoperator

Wenn eine benutzerdefinierte Klasse den Funktionsaufrufoperator operator) überlädt, wird sie zu einem FunctionObject-Typ.

Ein Objekt eines solchen Typs kann in einem Funktionsaufrufausdruck verwendet werden

// An object of this type represents a linear function of one variable a * x + b.
struct Linear
{
    double a, b;
 
    double operator()(double x) const
    {
        return a * x + b;
    }
};
 
int main()
{
    Linear f{2, 1};  // Represents function 2x + 1.
    Linear g{-1, 0}; // Represents function -x.
    // f and g are objects that can be used like a function.
 
    double f_0 = f(0);
    double f_1 = f(1);
 
    double g_0 = g(0);
}

Viele Standardbibliotheks-Algorithmen akzeptieren FunctionObjects, um das Verhalten anzupassen. Es gibt keine besonders hervorzuhebenden kanonischen Formen von operator), aber zur Veranschaulichung der Verwendung

#include <algorithm>
#include <iostream>
#include <vector>
 
struct Sum
{
    int sum = 0;
    void operator()(int n) { sum += n; }
};
 
int main()
{
    std::vector<int> v = {1, 2, 3, 4, 5};
    Sum s = std::for_each(v.begin(), v.end(), Sum());
    std::cout << "The sum is " << s.sum << '\n';
}

Ausgabe

The sum is 15

[edit] Inkrement und Dekrement

Wenn der Postfix-Inkrement- oder -Dekrementoperator in einem Ausdruck vorkommt, wird die entsprechende benutzerdefinierte Funktion (operator++ oder operator--) mit einem ganzzahligen Argument 0 aufgerufen. Typischerweise wird sie als T operator++(int) oder T operator--(int) deklariert, wobei das Argument ignoriert wird. Die Postfix-Inkrement- und -Dekrementoperatoren werden normalerweise in Bezug auf die Präfixversionen implementiert

struct X
{
    // prefix increment
    X& operator++()
    {
        // actual increment takes place here
        return *this; // return new value by reference
    }
 
    // postfix increment
    X operator++(int)
    {
        X old = *this; // copy old value
        operator++();  // prefix increment
        return old;    // return old value
    }
 
    // prefix decrement
    X& operator--()
    {
        // actual decrement takes place here
        return *this; // return new value by reference
    }
 
    // postfix decrement
    X operator--(int)
    {
        X old = *this; // copy old value
        operator--();  // prefix decrement
        return old;    // return old value
    }
};

Obwohl die kanonischen Implementierungen der Präfix-Inkrement- und -Dekrementoperatoren per Referenz zurückgeben, ist der Rückgabetyp wie bei jeder Operatorüberladung benutzerdefiniert; beispielsweise geben die Überladungen dieser Operatoren für std::atomic per Wert zurück.

[edit] Binäre arithmetische Operatoren

Binäre Operatoren werden typischerweise als Nicht-Member implementiert, um die Symmetrie zu wahren (z. B. beim Addieren einer komplexen Zahl und einer Ganzzahl, wenn operator+ eine Memberfunktion des komplexen Typs ist, dann würde nur complex + integer kompiliert werden, und nicht integer + complex). Da für jeden binären arithmetischen Operator ein entsprechender zusammengesetzter Zuweisungsoperator existiert, werden kanonische Formen von binären Operatoren in Bezug auf ihre zusammengesetzten Zuweisungen implementiert

class X
{
public:
    X& operator+=(const X& rhs) // compound assignment (does not need to be a member,
    {                           // but often is, to modify the private members)
        /* addition of rhs to *this takes place here */
        return *this; // return the result by reference
    }
 
    // friends defined inside class body are inline and are hidden from non-ADL lookup
    friend X operator+(X lhs,        // passing lhs by value helps optimize chained a+b+c
                       const X& rhs) // otherwise, both parameters may be const references
    {
        lhs += rhs; // reuse compound assignment
        return lhs; // return the result by value (uses move constructor)
    }
};

[edit] Vergleichsoperatoren

Standardbibliotheksalgorithmen wie std::sort und Container wie std::set erwarten, dass für benutzerdefinierte Typen standardmäßig operator< definiert ist, und erwarten, dass dieser eine strikte schwache Ordnung implementiert (wodurch die Compare-Anforderungen erfüllt werden). Eine idiomatische Methode zur Implementierung einer strikten schwachen Ordnung für eine Struktur ist die Verwendung des lexikographischen Vergleichs, der von std::tie bereitgestellt wird

struct Record
{
    std::string name;
    unsigned int floor;
    double weight;
 
    friend bool operator<(const Record& l, const Record& r)
    {
        return std::tie(l.name, l.floor, l.weight)
             < std::tie(r.name, r.floor, r.weight); // keep the same order
    }
};

Typischerweise werden, sobald operator< bereitgestellt ist, die anderen relationalen Operatoren in Bezug auf operator< implementiert.

inline bool operator< (const X& lhs, const X& rhs) { /* do actual comparison */ }
inline bool operator> (const X& lhs, const X& rhs) { return rhs < lhs; }
inline bool operator<=(const X& lhs, const X& rhs) { return !(lhs > rhs); }
inline bool operator>=(const X& lhs, const X& rhs) { return !(lhs < rhs); }

Ebenso wird der Ungleichheitsoperator typischerweise in Bezug auf operator== implementiert

inline bool operator==(const X& lhs, const X& rhs) { /* do actual comparison */ }
inline bool operator!=(const X& lhs, const X& rhs) { return !(lhs == rhs); }

Wenn ein Drei-Wege-Vergleich (wie std::memcmp oder std::string::compare) bereitgestellt wird, können alle sechs Zwei-Wege-Vergleichsoperatoren daraus ausgedrückt werden

inline bool operator==(const X& lhs, const X& rhs) { return cmp(lhs,rhs) == 0; }
inline bool operator!=(const X& lhs, const X& rhs) { return cmp(lhs,rhs) != 0; }
inline bool operator< (const X& lhs, const X& rhs) { return cmp(lhs,rhs) <  0; }
inline bool operator> (const X& lhs, const X& rhs) { return cmp(lhs,rhs) >  0; }
inline bool operator<=(const X& lhs, const X& rhs) { return cmp(lhs,rhs) <= 0; }
inline bool operator>=(const X& lhs, const X& rhs) { return cmp(lhs,rhs) >= 0; }

[edit] Array-Indexoperator

Benutzerdefinierte Klassen, die einen array-ähnlichen Zugriff bieten, der sowohl Lesen als auch Schreiben ermöglicht, definieren typischerweise zwei Überladungen für operator[]: const- und non-const-Varianten

struct T
{
          value_t& operator[](std::size_t idx)       { return mVector[idx]; }
    const value_t& operator[](std::size_t idx) const { return mVector[idx]; }
};

Alternativ können sie als einzelne Member-Funktionsschablone mit einem expliziten Objektparameter ausgedrückt werden

struct T
{
    decltype(auto) operator[](this auto& self, std::size_t idx) 
    { 
        return self.mVector[idx]; 
    }
};
(seit C++23)

Wenn der Werttyp als skalarer Typ bekannt ist, sollte die const-Variante per Wert zurückgeben.

Wo ein direkter Zugriff auf die Elemente des Containers nicht gewünscht oder nicht möglich ist oder zwischen der lvalue c[i] = v; und der rvalue v = c[i]; Verwendung unterschieden werden muss, kann operator[] ein Proxy zurückgeben. Siehe z. B. std::bitset::operator[].

operator[] kann nur einen Index aufnehmen. Um multidimensionale Array-Zugriffssemantik zu bieten, z. B. um einen 3D-Array-Zugriff a[i][j][k] = x; zu implementieren, muss operator[] eine Referenz auf eine 2D-Ebene zurückgeben, die ihren eigenen operator[] haben muss, der eine Referenz auf eine 1D-Zeile zurückgibt, die operator[] haben muss, der eine Referenz auf das Element zurückgibt. Um diese Komplexität zu vermeiden, entscheiden sich einige Bibliotheken stattdessen für die Überladung von operator(), so dass 3D-Zugriffsausdrücke die Fortran-ähnliche Syntax a(i, j, k) = x; haben.

(bis C++23)

operator[] kann beliebig viele Indizes aufnehmen. Zum Beispiel kann ein operator[] einer 3D-Array-Klasse, deklariert als T& operator[](std::size_t x, std::size_t y, std::size_t z); direkt auf die Elemente zugreifen.

#include <array>
#include <cassert>
#include <iostream>
 
template<typename T, std::size_t Z, std::size_t Y, std::size_t X>
struct Array3d
{
    std::array<T, X * Y * Z> m{};
 
    constexpr T& operator[](std::size_t z, std::size_t y, std::size_t x) // C++23
    {
        assert(x < X and y < Y and z < Z);
        return m[z * Y * X + y * X + x];
    }
};
 
int main()
{
    Array3d<int, 4, 3, 2> v;
    v[3, 2, 1] = 42;
    std::cout << "v[3, 2, 1] = " << v[3, 2, 1] << '\n';
}

Ausgabe

v[3, 2, 1] = 42
(seit C++23)

[edit] Bitweise arithmetische Operatoren

Benutzerdefinierte Klassen und Aufzählungen, die die Anforderungen an BitmaskType erfüllen, müssen die bitweisen arithmetischen Operatoren operator&, operator|, operator^, operator~, operator&=, operator|= und operator^= überladen und können optional die Schiebeoperatoren operator<< operator>>, operator>>= und operator<<= überladen. Die kanonischen Implementierungen folgen normalerweise dem Muster für binäre arithmetische Operatoren, das oben beschrieben wurde.

[edit] Boolescher Negationsoperator

Der Operator operator! wird häufig von benutzerdefinierten Klassen überladen, die für die Verwendung in booleschen Kontexten bestimmt sind. Solche Klassen stellen auch eine benutzerdefinierte Konvertierungsfunktion in den booleschen Typ bereit (siehe std::basic_ios als Beispiel in der Standardbibliothek) und das erwartete Verhalten von operator! ist, den Wert zu liefern, der dem von operator bool entgegengesetzt ist.

(bis C++11)

Da der eingebaute Operator ! eine kontextbezogene Konvertierung in bool durchführt, können benutzerdefinierte Klassen, die für die Verwendung in booleschen Kontexten bestimmt sind, nur operator bool bereitstellen und müssen operator! nicht überladen.

(seit C++11)

[edit] Selten überladene Operatoren

Die folgenden Operatoren werden selten überladen

  • Der Adressoperator, operator&. Wenn das unäre & auf ein lvalue vom unvollständigen Typ angewendet wird und der vollständige Typ einen überladenen operator& deklariert, ist es undefiniert, ob der Operator die eingebaute Bedeutung hat oder die Operatorfunktion aufgerufen wird. Da dieser Operator überladen werden kann, verwenden generische Bibliotheken std::addressof, um Adressen von Objekten benutzerdefinierter Typen zu erhalten. Das bekannteste Beispiel für einen kanonischen überladenen operator& ist die Microsoft-Klasse CComPtrBase. Ein Beispiel für die Verwendung dieses Operators in EDSL findet sich in boost.spirit.
  • Die booleschen Logikoperatoren, operator&& und operator||. Im Gegensatz zu den eingebauten Versionen können die Überladungen keine Kurzschlussauswertung implementieren.Im Gegensatz zu den eingebauten Versionen sequenzieren sie auch nicht ihren linken Operanden vor dem rechten.(bis C++17) In der Standardbibliothek werden diese Operatoren nur für std::valarray überladen.
  • Der Kommaoperator, operator,. Im Gegensatz zur eingebauten Version sequenzieren die Überladungen ihren linken Operanden nicht vor dem rechten.(bis C++17) Da dieser Operator überladen werden kann, verwenden generische Bibliotheken Ausdrücke wie a, void(), b anstelle von a, b, um die Ausführung von Ausdrücken benutzerdefinierter Typen zu sequenzieren. Die Boost-Bibliothek verwendet operator, in boost.assign, boost.spirit und anderen Bibliotheken. Die Datenbankzugriffsbibliothek SOCI überlädt ebenfalls operator,.
  • Der Mitgliedszugriff über Zeiger auf Mitglied operator->*. Es gibt keine spezifischen Nachteile beim Überladen dieses Operators, aber er wird in der Praxis selten verwendet. Es wurde vorgeschlagen, dass er Teil einer Smart-Pointer-Schnittstelle sein könnte und wird tatsächlich in dieser Funktion von Akteuren in boost.phoenix verwendet. Er ist in EDSLs wie cpp.react häufiger anzutreffen.

[edit] Anmerkungen

Feature-Test-Makro Wert Std Feature
__cpp_static_call_operator 202207L (C++23) static operator()
__cpp_multidimensional_subscript 202211L (C++23) static operator[]

[edit] Schlüsselwörter

operator

[edit] Beispiel

#include <iostream>
 
class Fraction
{
    // or C++17's std::gcd
    constexpr int gcd(int a, int b) { return b == 0 ? a : gcd(b, a % b); }
 
    int n, d;
public:
    constexpr Fraction(int n, int d = 1) : n(n / gcd(n, d)), d(d / gcd(n, d)) {}
 
    constexpr int num() const { return n; }
    constexpr int den() const { return d; }
 
    constexpr Fraction& operator*=(const Fraction& rhs)
    {
        int new_n = n * rhs.n / gcd(n * rhs.n, d * rhs.d);
        d = d * rhs.d / gcd(n * rhs.n, d * rhs.d);
        n = new_n;
        return *this;
    }
};
 
std::ostream& operator<<(std::ostream& out, const Fraction& f)
{
   return out << f.num() << '/' << f.den();
}
 
constexpr bool operator==(const Fraction& lhs, const Fraction& rhs)
{
    return lhs.num() == rhs.num() && lhs.den() == rhs.den();
}
 
constexpr bool operator!=(const Fraction& lhs, const Fraction& rhs)
{
    return !(lhs == rhs);
}
 
constexpr Fraction operator*(Fraction lhs, const Fraction& rhs)
{
    return lhs *= rhs;
}
 
int main()
{
    constexpr Fraction f1{3, 8}, f2{1, 2}, f3{10, 2};
    std::cout << f1 << " * " << f2 << " = " << f1 * f2 << '\n'
              << f2 << " * " << f3 << " = " << f2 * f3 << '\n'
              <<  2 << " * " << f1 << " = " <<  2 * f1 << '\n';
    static_assert(f3 == f2 * 10);
}

Ausgabe

3/8 * 1/2 = 3/16
1/2 * 5/1 = 5/2
2 * 3/8 = 3/4

[bearbeiten] Defect reports

Die folgenden Verhaltensändernden Fehlerberichte wurden rückwirkend auf zuvor veröffentlichte C++-Standards angewendet.

DR angewendet auf Verhalten wie veröffentlicht Korrigiertes Verhalten
CWG 1481 C++98 der nicht-Member-Präfix-Inkrement-Operator konnte nur einen Parameter
vom Klassentyp, Enumerationstyp oder einem Referenztyp auf solche Typen haben
keine Typanforderung
CWG 2931 C++23 explizite Objekt-Member-Operatorfunktionen konnten keinen Parameter haben
vom Klassentyp, Enumerationstyp oder einem Referenztyp auf solche Typen haben
verboten

[bearbeiten] Siehe auch

Häufige Operatoren
Zuweisung Inkrement
Dekrement
Arithmetik Logisch Vergleich Member
Zugriff
Sonstiges

a = b
a += b
a -= b
a *= b
a /= b
a %= b
a &= b
a |= b
a ^= b
a <<= b
a >>= b

++a
--a
a++
a--

+a
-a
a + b
a - b
a * b
a / b
a % b
~a
a & b
a | b
a ^ b
a << b
a >> b

!a
a && b
a || b

a == b
a != b
a < b
a > b
a <= b
a >= b
a <=> b

a[...]
*a
&a
a->b
a.b
a->*b
a.*b

Funktionsaufruf

a(...)
Komma

a, b
Ternär

a ? b : c
Spezielle Operatoren

static_cast konvertiert einen Typ in einen anderen verwandten Typ
dynamic_cast konvertiert innerhalb von Vererbungshierarchien
const_cast fügt cv-Qualifizierer hinzu oder entfernt sie
reinterpret_cast konvertiert einen Typ in einen nicht verwandten Typ
C-Stil Cast konvertiert einen Typ in einen anderen durch eine Mischung aus static_cast, const_cast und reinterpret_cast
new erstellt Objekte mit dynamischer Speicherverwaltung
delete zerstört zuvor mit new-Ausdruck erstellte Objekte und gibt den zugewiesenen Speicherbereich frei
sizeof fragt die Größe eines Typs ab
sizeof... fragt die Größe eines packs ab (seit C++11)
typeid fragt die Typinformationen eines Typs ab
noexcept prüft, ob ein Ausdruck eine Ausnahme auslösen kann (seit C++11)
alignof fragt die Ausrichtungsvoraussetzungen eines Typs ab (seit C++11)

[bearbeiten] Externe Links

  1. Operator Overloading auf StackOverflow C++ FAQ