Suche…


Einführung

In C ++ können Operatoren wie + und -> für benutzerdefinierte Typen definiert werden. Beispielsweise definiert der Header <string> einen + -Operator zum Verketten von Zeichenfolgen. Dazu wird eine Operatorfunktion mit dem operator Schlüsselwort definiert .

Bemerkungen

Die Operatoren für integrierte Typen können nicht geändert werden. Operatoren können nur für benutzerdefinierte Typen überladen werden. Das heißt, mindestens einer der Operanden muss von einem benutzerdefinierten Typ sein.

Die folgenden Operatoren können nicht überladen werden:

  • Der Mitgliederzugriff oder "Punkt" -Operator .
  • Der Zeiger auf den Mitgliederzugriffsoperator .*
  • Der Operator für die Bereichsauflösung ::
  • Der ternäre bedingte Operator ?:
  • dynamic_cast , static_cast , reinterpret_cast , const_cast , typeid , sizeof , alignof und noexcept
  • Die Vorverarbeitungs-Direktiven # und ## , die ausgeführt werden, bevor Typinformationen verfügbar sind.

Es gibt einige Operatoren, die Sie nicht (99,98% der Zeit) überlasten sollten:

  • && und || (bevorzugen Sie stattdessen die implizite Konvertierung in bool )
  • ,
  • Die Adresse des Operators (unary & )

Warum? Sie überladen Operatoren, die ein anderer Programmierer möglicherweise niemals erwartet, was zu einem anderen Verhalten als erwartet führt.

Zum Beispiel die benutzerdefinierten && und || Überlastungen dieser Operatoren ihre Kurzauswertung verlieren und ihre speziellen Eigenschaften Sequenzierung (C ++ 17) verlieren , gilt die Sequenzierung Problem auch , Betreiber Überlastung.

Rechenzeichen

Sie können alle grundlegenden arithmetischen Operatoren überladen:

  • + und +=
  • - und -=
  • * und *=
  • / und /=
  • & und &=
  • | und |=
  • ^ und ^=
  • >> und >>=
  • << und <<=

Das Überladen für alle Bediener ist das gleiche. Scrollen Sie zur Erklärung nach unten

Überladung außerhalb der class / struct :

//operator+ should be implemented in terms of operator+=
T operator+(T lhs, const T& rhs)
{
    lhs += rhs;
    return lhs;
}

T& operator+=(T& lhs, const T& rhs)
{
    //Perform addition
    return lhs;
}

Überladung innerhalb von class / struct :

//operator+ should be implemented in terms of operator+=
T operator+(const T& rhs)
{
    *this += rhs;
    return *this;
}

T& operator+=(const T& rhs)
{
    //Perform addition
    return *this;
}

Hinweis: operator+ sollte nicht konstanten Wert zurück, als eine Referenz Rückkehr nicht Sinn machen würde (es ein neues Objekt zurückgibt) , noch würde eine Rückkehr const Wert (Sie sollten in der Regel nicht wieder durch const ). Das erste Argument wird als Wert übergeben, warum? weil

  1. Sie können das ursprüngliche Objekt nicht ändern ( Object foobar = foo + bar; sollte foo nicht ändern, es wäre nicht sinnvoll)
  2. Sie können es nicht als const , da Sie das Objekt modifizieren müssen (weil operator+ als operator+= implementiert ist, wodurch das Objekt geändert wird).

Das Übergeben von const& wäre eine Option, aber dann müssen Sie eine temporäre Kopie des übergebenen Objekts erstellen. Durch die Übergabe durch value erledigt der Compiler dies für Sie.


operator+= gibt einen Verweis auf sich selbst zurück, da es dann möglich ist, sie zu verketten (verwenden Sie jedoch nicht dieselbe Variable, da dies aufgrund von Sequenzpunkten undefiniertes Verhalten wäre).

Das erste Argument ist eine Referenz (wir möchten sie ändern), aber nicht const , weil Sie sie dann nicht ändern können. Das zweite Argument sollte nicht geändert werden und wird daher aus Leistungsgründen von const& (das Übergeben von const-Referenz ist schneller als nach Wert).

Unäre Operatoren

Sie können die beiden unären Operatoren überladen:

  • ++foo und foo++
  • --foo und foo--

Das Überladen ist für beide Typen ( ++ und -- ) gleich. Scrollen Sie zur Erklärung nach unten

Überladung außerhalb der class / struct :

//Prefix operator ++foo
T& operator++(T& lhs)
{
    //Perform addition
    return lhs;
}

//Postfix operator foo++ (int argument is used to separate pre- and postfix) 
//Should be implemented in terms of ++foo (prefix operator)
T operator++(T& lhs, int)
{
    T t(lhs);
    ++lhs;
    return t;
}

Überladung innerhalb von class / struct :

//Prefix operator ++foo
T& operator++()
{
    //Perform addition
    return *this;
}

//Postfix operator foo++ (int argument is used to separate pre- and postfix) 
//Should be implemented in terms of ++foo (prefix operator)
T operator++(int)
{
    T t(*this);
    ++(*this);
    return t;
}

Hinweis: Der Präfixoperator gibt einen Verweis auf sich selbst zurück, sodass Sie die Vorgänge darauf fortsetzen können. Das erste Argument ist eine Referenz, da der Präfixoperator das Objekt ändert. Dies ist auch der Grund, warum es nicht const (sonst könnten Sie es nicht ändern).


Der Postfix-Operator gibt als Wert einen temporären Wert (den vorherigen Wert) zurück. Daher kann es sich nicht um einen Verweis handeln, da es sich um einen temporären Verweis handelt, der am Ende der Funktion ein Wert für die Verwendung von Darbietungen ist, da die temporäre Variable ausgeht Geltungsbereich). Es kann auch nicht const , da Sie es direkt ändern können sollten.

Das erste Argument ist eine nicht const Referenz auf das "aufrufende" Objekt, denn wenn es const wäre, könnten Sie es nicht ändern, und wenn es keine Referenz wäre, würden Sie den ursprünglichen Wert nicht ändern.

Es ist wegen des Kopierens in Postfix - Operator benötigt Überlastungen , dass es besser ist , es sich zur Gewohnheit zu machen Präfix ++ zu verwenden anstelle von Postfix ++ in for Schleifen. Aus der for Schleife-Perspektive sind sie normalerweise funktional gleichwertig, aber es kann einen geringfügigen Leistungsvorteil bei der Verwendung von Präfix ++ geben, insbesondere bei "fetten" Klassen mit vielen zu kopierenden Elementen. Beispiel für die Verwendung von Präfix ++ in einer for-Schleife:

for (list<string>::const_iterator it = tokens.begin();
     it != tokens.end();
     ++it) { // Don't use it++
    ...
}

Vergleichsoperatoren

Sie können alle Vergleichsoperatoren überladen:

  • == und !=
  • > und <
  • >= und <=

Es wird empfohlen, alle diese Operatoren zu überladen, indem Sie nur zwei Operatoren ( == und < ) implementieren und diese dann verwenden, um den Rest zu definieren. Scrollen Sie zur Erklärung nach unten

Überladung außerhalb der class / struct :

//Only implement those 2
bool operator==(const T& lhs, const T& rhs) { /* Compare */ }
bool operator<(const T& lhs, const T& rhs) { /* Compare */ }

//Now you can define the rest
bool operator!=(const T& lhs, const T& rhs) { return !(lhs == rhs); }
bool operator>(const T& lhs, const T& rhs) { return rhs < lhs; }
bool operator<=(const T& lhs, const T& rhs) { return !(lhs > rhs); }
bool operator>=(const T& lhs, const T& rhs) { return !(lhs < rhs); }

Überladung innerhalb von class / struct :

//Note that the functions are const, because if they are not const, you wouldn't be able
//to call them if the object is const

//Only implement those 2
bool operator==(const T& rhs) const { /* Compare */ }
bool operator<(const T& rhs) const { /* Compare */ }

//Now you can define the rest
bool operator!=(const T& rhs) const { return !(*this == rhs); }
bool operator>(const T& rhs) const { return rhs < *this; }
bool operator<=(const T& rhs) const { return !(*this > rhs); }
bool operator>=(const T& rhs) const { return !(*this < rhs); }

Die Operatoren geben offensichtlich ein bool und geben für die entsprechende Operation true oder false an.

Alle Operatoren verwenden ihre Argumente durch const& , da die Operatoren nur Vergleichen durchführen und daher die Objekte nicht ändern dürfen. Das Übergeben von & (Referenz) ist schneller als mit dem Wert. Um sicherzustellen, dass die Operatoren es nicht ändern, handelt es sich um eine const Referenz.

Beachten Sie, dass die Operatoren in der class / struct als const definiert sind. Der Grund dafür ist, dass ohne die Funktionen von const Vergleich von const Objekten nicht möglich ist, da der Compiler nicht weiß, dass die Operatoren nichts ändern.

Konvertierungsoperatoren

Sie können Typoperatoren überladen, sodass Ihr Typ implizit in den angegebenen Typ konvertiert werden kann.

Der Konvertierungsoperator muss in einer class / struct :

operator T() const { /* return something */ }

Hinweis: Der Operator ist const , damit const Objekte konvertiert werden können.

Beispiel:

struct Text
{
    std::string text;

    // Now Text can be implicitly converted into a const char*
    /*explicit*/ operator const char*() const { return text.data(); }
    // ^^^^^^^
    // to disable implicit conversion
};

Text t;
t.text = "Hello world!";

//Ok
const char* copyoftext = t;

Array-Indexoperator

Sie können sogar den Array-Indexoperator [] überladen.

Sie sollten immer (99,98% der Zeit) implementieren 2 Versionen, eine const und eine nicht- const Version, denn wenn das Objekt const , es nicht in der Lage sein sollte , das Objekt zurückgegeben von ändern [] .

Die Argumente werden von const& anstatt von value übergeben, da die Übergabe durch Verweis schneller ist als durch value und const sodass der Operator den Index nicht aus Versehen ändert.

Die Operatoren werden als Referenz zurückgegeben, da Sie das Objekt [] return modifizieren können, dh:

std::vector<int> v{ 1 };
v[0] = 2; //Changes value of 1 to 2
          //wouldn't be possible if not returned by reference

Sie können nur innerhalb einer class / struct überladen:

//I is the index type, normally an int
T& operator[](const I& index)
{
    //Do something
    //return something
}

//I is the index type, normally an int
const T& operator[](const I& index) const
{
    //Do something
    //return something
}

Mehrere Subskriptionsoperatoren [][]... können über Proxy-Objekte erreicht werden. Das folgende Beispiel einer einfachen Matrixmatrix mit Zeilenmaxiven veranschaulicht dies:

template<class T>
class matrix {
    // class enabling [][] overload to access matrix elements
    template <class C>
    class proxy_row_vector {
        using reference = decltype(std::declval<C>()[0]);
        using const_reference = decltype(std::declval<C const>()[0]);
    public:
        proxy_row_vector(C& _vec, std::size_t _r_ind, std::size_t _cols)
            : vec(_vec), row_index(_r_ind), cols(_cols) {}
        const_reference operator[](std::size_t _col_index) const {
            return vec[row_index*cols + _col_index];
        }
        reference operator[](std::size_t _col_index) {
            return vec[row_index*cols + _col_index];
        }
    private:
        C& vec;
        std::size_t row_index; // row index to access
        std::size_t cols; // number of columns in matrix
    };

    using const_proxy = proxy_row_vector<const std::vector<T>>;
    using proxy = proxy_row_vector<std::vector<T>>;
public:
    matrix() : mtx(), rows(0), cols(0) {}
    matrix(std::size_t _rows, std::size_t _cols)
        : mtx(_rows*_cols), rows(_rows), cols(_cols) {}

    // call operator[] followed by another [] call to access matrix elements
    const_proxy operator[](std::size_t _row_index) const {
        return const_proxy(mtx, _row_index, cols);
    }

    proxy operator[](std::size_t _row_index) {
        return proxy(mtx, _row_index, cols);
    }
private:
    std::vector<T> mtx;
    std::size_t rows;
    std::size_t cols;
};

Funktionsaufruf-Operator

Sie können den Funktionsaufrufoperator () überladen:

Das Überladen muss innerhalb einer class / struct :

//R -> Return type
//Types -> any different type
R operator()(Type name, Type2 name2, ...)
{
    //Do something
    //return something
}

//Use it like this (R is return type, a and b are variables)
R foo = object(a, b, ...);

Zum Beispiel:

struct Sum
{
    int operator()(int a, int b)
    {
        return a + b;
    }
};

//Create instance of struct
Sum sum;
int result = sum(1, 1); //result == 2

Aufgabenverwalter

Der Zuweisungsoperator ist einer der wichtigsten Operatoren, da Sie den Status einer Variablen ändern können.

Wenn Sie den Assigment-Operator für Ihre class / struct nicht überladen, wird er automatisch vom Compiler generiert: Der automatisch generierte Zuweisungsoperator führt eine "memberwise-Zuweisung" durch, dh durch Aufrufen von Zuweisungsoperatoren für alle Member, sodass ein Objekt kopiert wird zum anderen ein Mitglied zur Zeit. Der Zuweisungsoperator sollte überlastet sein, wenn die einfache memberweise Zuweisung für Ihre class / struct nicht geeignet ist, beispielsweise wenn Sie eine tiefe Kopie eines Objekts erstellen müssen.

Das Überladen des Zuweisungsoperators = ist einfach, aber Sie sollten einige einfache Schritte ausführen.

  1. Testen Sie die Selbstzuweisung. Diese Überprüfung ist aus zwei Gründen wichtig:
    • Eine Selbstzuweisung ist eine unnötige Kopie, daher macht es keinen Sinn, sie auszuführen.
    • Der nächste Schritt wird bei einer Selbstzuweisung nicht funktionieren.
  2. Bereinigen Sie die alten Daten. Die alten Daten müssen durch neue ersetzt werden. Nun können Sie den zweiten Grund des vorherigen Schritts verstehen: Wenn der Inhalt des Objekts zerstört wurde, kann die Kopie nicht durch eine Selbstzuweisung ausgeführt werden.
  3. Kopieren Sie alle Mitglieder. Wenn Sie den Assigment-Operator für Ihre class oder Ihre struct überladen, wird er nicht automatisch vom Compiler generiert. Sie müssen also alle Member aus dem anderen Objekt kopieren.
  4. Gib *this . Der Operator kehrt per Referenz von selbst zurück, da er die Verkettung ermöglicht (dh int b = (a = 6) + 4; //b == 10 ).
//T is some type
T& operator=(const T& other)
{
    //Do something (like copying values)
    return *this;
}

Anmerkung: other wird von const& , da das zugewiesene Objekt nicht geändert werden sollte und die Referenzübergabe schneller ist als durch den Wert. Um sicherzustellen, dass operator= es nicht versehentlich ändert, ist es const .

Der Zuweisungsoperator kann nur in der class / struct überladen werden, da der linke Wert von = immer die class / struct selbst ist. Das Definieren als freie Funktion hat diese Garantie nicht und ist daher nicht zulässig.

Wenn Sie es in der class / struct deklarieren, ist der linke Wert implizit die class / struct selbst, sodass dies kein Problem darstellt.

Bitweiser NICHT Operator

Das bitweise NOT ( ~ ) zu überladen ist ziemlich einfach. Scrollen Sie zur Erklärung nach unten

Überladung außerhalb der class / struct :

T operator~(T lhs)
{
    //Do operation
    return lhs;
}

Überladung innerhalb von class / struct :

T operator~()
{
    T t(*this);
    //Do operation
    return t;
}

Hinweis: Der operator~ gibt den Wert zurück, da er einen neuen Wert (den geänderten Wert) und nicht einen Verweis auf den Wert zurückgeben muss (dies wäre ein Verweis auf das temporäre Objekt, in dem sich sobald ein Wert für den Wert befindet der Operator ist fertig). Auch nicht const da der aufrufende Code ihn nachträglich ändern kann (dh int a = ~a + 1; sollte möglich sein).

Innerhalb der class / struct Sie ein temporäres Objekt erstellen, da Sie dies nicht ändern this , da dies das ursprüngliche Objekt ändern würde. this sollte jedoch nicht der Fall sein.

Bit-Shift-Operatoren für E / A

Die Operatoren << und >> werden üblicherweise als "Write" - und "Read" -Operatoren verwendet:

  • std::ostream << , um Variablen in den zugrunde liegenden Stream zu schreiben (Beispiel: std::cout )
  • std::istream überladen >> , um aus dem zugrunde liegenden Stream eine Variable zu lesen (Beispiel: std::cin )

Die Vorgehensweise ist ähnlich, wenn Sie sie "normal" außerhalb der class / struct überladen struct , mit der Ausnahme, dass die Angabe der Argumente nicht vom selben Typ ist:

  • Der Rückgabetyp ist der Stream, den Sie überladen möchten (z. B. std::ostream ), um eine Verkettung zuzulassen (Chaining: std::cout << a << b; ). Beispiel: std::ostream&
  • lhs wäre das gleiche wie der Rückgabetyp
  • rhs ist der Typ, für den Sie eine Überladung zulassen möchten (z. B. T ), der von const& anstelle des Werts aus Leistungsgründen übergeben wird ( rhs sollte sowieso nicht geändert werden). Beispiel: const Vector& .

Beispiel:

//Overload std::ostream operator<< to allow output from Vector's
std::ostream& operator<<(std::ostream& lhs, const Vector& rhs)
{
    lhs << "x: " << rhs.x << " y: " << rhs.y << " z: " << rhs.z << '\n';
    return lhs;
}

Vector v = { 1, 2, 3};

//Now you can do
std::cout << v;

Komplexe Zahlen werden erneut betrachtet

Mit dem folgenden Code wird ein sehr einfacher komplexer Zahlentyp implementiert, für den das zugrunde liegende Feld gemäß den Regeln für die Typumwandlung der Sprache unter Verwendung der vier Basisoperatoren (+, -, * und /) mit einem Mitglied eines anderen Felds automatisch hochgestuft wird (sei es ein anderer complex<T> oder ein anderer Skalartyp).

Dies soll ein ganzheitliches Beispiel sein, das die Überlastung der Bediener und die grundlegende Verwendung von Vorlagen abdeckt.

#include <type_traits>

namespace not_std{

using std::decay_t;

//----------------------------------------------------------------
// complex< value_t >
//----------------------------------------------------------------

template<typename value_t>
struct complex
{
    value_t x;
    value_t y;

    complex &operator += (const value_t &x)
    {
        this->x += x;
        return *this;
    }
    complex &operator += (const complex &other)
    {
        this->x += other.x;
        this->y += other.y;
        return *this;
    }

    complex &operator -= (const value_t &x)
    {
        this->x -= x;
        return *this;
    }
    complex &operator -= (const complex &other)
    {
        this->x -= other.x;
        this->y -= other.y;
        return *this;
    }

    complex &operator *= (const value_t &s)
    {
        this->x *= s;
        this->y *= s;
        return *this;
    }
    complex &operator *= (const complex &other)
    {
        (*this) = (*this) * other;
        return *this;
    }

    complex &operator /= (const value_t &s)
    {
        this->x /= s;
        this->y /= s;
        return *this;
    }
    complex &operator /= (const complex &other)
    {
        (*this) = (*this) / other;
        return *this;
    }

    complex(const value_t &x, const value_t &y)
    : x{x}
    , y{y}
    {}

    template<typename other_value_t>
    explicit complex(const complex<other_value_t> &other)
    : x{static_cast<const value_t &>(other.x)}
    , y{static_cast<const value_t &>(other.y)}
    {}

    complex &operator = (const complex &) = default;
    complex &operator = (complex &&) = default;
    complex(const complex &) = default;
    complex(complex &&) = default;
    complex() = default;
};

// Absolute value squared
template<typename value_t>
value_t absqr(const complex<value_t> &z)
{ return z.x*z.x + z.y*z.y; }

//----------------------------------------------------------------
// operator - (negation)
//----------------------------------------------------------------

template<typename value_t>
complex<value_t> operator - (const complex<value_t> &z)
{ return {-z.x, -z.y}; }

//----------------------------------------------------------------
// operator +
//----------------------------------------------------------------

template<typename left_t,typename right_t>
auto operator + (const complex<left_t> &a, const complex<right_t> &b)
-> complex<decay_t<decltype(a.x + b.x)>>
{ return{a.x + b.x, a.y + b.y}; }

template<typename left_t,typename right_t>
auto operator + (const left_t &a, const complex<right_t> &b)
-> complex<decay_t<decltype(a + b.x)>>
{ return{a + b.x, b.y}; }

template<typename left_t,typename right_t>
auto operator + (const complex<left_t> &a, const right_t &b)
-> complex<decay_t<decltype(a.x + b)>>
{ return{a.x + b, a.y}; }

//----------------------------------------------------------------
// operator -
//----------------------------------------------------------------

template<typename left_t,typename right_t>
auto operator - (const complex<left_t> &a, const complex<right_t> &b)
-> complex<decay_t<decltype(a.x - b.x)>>
{ return{a.x - b.x, a.y - b.y}; }

template<typename left_t,typename right_t>
auto operator - (const left_t &a, const complex<right_t> &b)
-> complex<decay_t<decltype(a - b.x)>>
{ return{a - b.x, - b.y}; }

template<typename left_t,typename right_t>
auto operator - (const complex<left_t> &a, const right_t &b)
-> complex<decay_t<decltype(a.x - b)>>
{ return{a.x - b, a.y}; }

//----------------------------------------------------------------
// operator *
//----------------------------------------------------------------

template<typename left_t, typename right_t>
auto operator * (const complex<left_t> &a, const complex<right_t> &b)
-> complex<decay_t<decltype(a.x * b.x)>>
{
    return {
        a.x*b.x - a.y*b.y,
        a.x*b.y + a.y*b.x
        };
}

template<typename left_t, typename right_t>
auto operator * (const left_t &a, const complex<right_t> &b)
-> complex<decay_t<decltype(a * b.x)>>
{ return {a * b.x, a * b.y}; }

template<typename left_t, typename right_t>
auto operator * (const complex<left_t> &a, const right_t &b)
-> complex<decay_t<decltype(a.x * b)>>
{ return {a.x * b, a.y * b}; }

//----------------------------------------------------------------
// operator /
//----------------------------------------------------------------

template<typename left_t, typename right_t>
auto operator / (const complex<left_t> &a, const complex<right_t> &b)
-> complex<decay_t<decltype(a.x / b.x)>>
{
    const auto r = absqr(b);
    return {
        ( a.x*b.x + a.y*b.y) / r,
        (-a.x*b.y + a.y*b.x) / r
        };
}

template<typename left_t, typename right_t>
auto operator / (const left_t &a, const complex<right_t> &b)
-> complex<decay_t<decltype(a / b.x)>>
{
    const auto s = a/absqr(b);
    return {
         b.x * s,
        -b.y * s
        };
}

template<typename left_t, typename right_t>
auto operator / (const complex<left_t> &a, const right_t &b)
-> complex<decay_t<decltype(a.x / b)>>
{ return {a.x / b, a.y / b}; }

}// namespace not_std


int main(int argc, char **argv)
{
    using namespace not_std;

    complex<float> fz{4.0f, 1.0f};

    // makes a complex<double>
    auto dz = fz * 1.0;

    // still a complex<double>
    auto idz = 1.0f/dz;

    // also a complex<double>
    auto one = dz * idz;

    // a complex<double> again
    auto one_again = fz * idz;

    // Operator tests, just to make sure everything compiles.

    complex<float> a{1.0f, -2.0f};
    complex<double> b{3.0, -4.0};

    // All of these are complex<double>
    auto c0 = a + b;
    auto c1 = a - b;
    auto c2 = a * b;
    auto c3 = a / b;

    // All of these are complex<float>
    auto d0 = a + 1;
    auto d1 = 1 + a;
    auto d2 = a - 1;
    auto d3 = 1 - a;
    auto d4 = a * 1;
    auto d5 = 1 * a;
    auto d6 = a / 1;
    auto d7 = 1 / a;

    // All of these are complex<double>
    auto e0 = b + 1;
    auto e1 = 1 + b;
    auto e2 = b - 1;
    auto e3 = 1 - b;
    auto e4 = b * 1;
    auto e5 = 1 * b;
    auto e6 = b / 1;
    auto e7 = 1 / b;

    return 0;
}

Benannte Betreiber

Sie können C ++ mit benannten Operatoren erweitern, die von Standard-C ++ - Operatoren "in Anführungszeichen" gesetzt werden.

Zuerst beginnen wir mit einer Dutzend-Zeilen-Bibliothek:

namespace named_operator {
  template<class D>struct make_operator{constexpr make_operator(){}};

  template<class T, char, class O> struct half_apply { T&& lhs; };

  template<class Lhs, class Op>
  half_apply<Lhs, '*', Op> operator*( Lhs&& lhs, make_operator<Op> ) {
    return {std::forward<Lhs>(lhs)};
  }

  template<class Lhs, class Op, class Rhs>
  auto operator*( half_apply<Lhs, '*', Op>&& lhs, Rhs&& rhs )
  -> decltype( named_invoke( std::forward<Lhs>(lhs.lhs), Op{}, std::forward<Rhs>(rhs) ) )
  {
    return named_invoke( std::forward<Lhs>(lhs.lhs), Op{}, std::forward<Rhs>(rhs) );
  }
}

das tut noch nichts.

Zuerst werden Vektoren angehängt

namespace my_ns {
  struct append_t : named_operator::make_operator<append_t> {};
  constexpr append_t append{};
  
  template<class T, class A0, class A1>
  std::vector<T, A0> named_invoke( std::vector<T, A0> lhs, append_t, std::vector<T, A1> const& rhs ) {
      lhs.insert( lhs.end(), rhs.begin(), rhs.end() );
      return std::move(lhs);
  }
}
using my_ns::append;

std::vector<int> a {1,2,3};
std::vector<int> b {4,5,6};

auto c = a *append* b;

Der Kern hier ist, dass wir ein append Objekt vom Typ append_t:named_operator::make_operator<append_t> .

Dann überladen wir named_invoke (lhs, append_t, rhs) für die Typen, die wir rechts und links möchten.

Die Bibliothek überlastet lhs*append_t und gibt ein temporäres half_apply Objekt zurück. Außerdem wird half_apply*rhs überladen, um named_invoke( lhs, append_t, rhs ) .

Wir müssen einfach das richtige append_t Token erstellen und eine ADL-freundliche named_invoke mit der richtigen Signatur named_invoke , und alles hängt zusammen und funktioniert.

Angenommen, Sie möchten eine elementweise Multiplikation der Elemente eines std :: -Arrays haben:

template<class=void, std::size_t...Is>
auto indexer( std::index_sequence<Is...> ) {
  return [](auto&& f) {
    return f( std::integral_constant<std::size_t, Is>{}... );
  };
}
template<std::size_t N>
auto indexer() { return indexer( std::make_index_sequence<N>{} ); }

namespace my_ns {
  struct e_times_t : named_operator::make_operator<e_times_t> {};
  constexpr e_times_t e_times{};

  template<class L, class R, std::size_t N,
    class Out=std::decay_t<decltype( std::declval<L const&>()*std::declval<R const&>() )>
  >
  std::array<Out, N> named_invoke( std::array<L, N> const& lhs, e_times_t, std::array<R, N> const& rhs ) {
    using result_type = std::array<Out, N>;
    auto index_over_N = indexer<N>();
    return index_over_N([&](auto...is)->result_type {
      return {{
        (lhs[is] * rhs[is])...
      }};
    });
  }
}

Live-Beispiel .

Dieser elementweise Arraycode kann für Tupel oder Paare oder Arrays im C-Stil oder sogar für Container mit variabler Länge verwendet werden, wenn Sie entscheiden, was zu tun ist, wenn die Längen nicht übereinstimmen.

Sie könnten auch einen elementweisen Operator-Typ lhs *element_wise<'+'>* rhs und lhs *element_wise<'+'>* rhs .

Das Schreiben von *dot* und *cross* -Produktoperatoren ist ebenfalls eine offensichtliche Verwendung.

Die Verwendung von * kann erweitert werden, um andere Trennzeichen wie + . Die Delimetermeßgenauigkeit bestimmt die Genauigkeit des genannten Operators. Dies kann wichtig sein, wenn Physikgleichungen mit minimalem Einsatz von extra () s nach C ++ übersetzt werden.

Mit einer geringfügigen Änderung in der Bibliothek können wir ->*then* -Operatoren unterstützen und die std::function vor der Aktualisierung des Standards erweitern oder monadisch schreiben ->*bind* . Es könnte auch einen stateful-benannten Operator haben, bei dem wir das Op vorsichtig an die endgültige Aufruffunktion übergeben, was Folgendes ermöglicht:

named_operator<'*'> append = [](auto lhs, auto&& rhs) {
  using std::begin; using std::end;
  lhs.insert( end(lhs), begin(rhs), end(rhs) );
  return std::move(lhs);
};

einen benannten Container anhängenden Operator in C ++ 17 erzeugen.



Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow