Ricerca…


introduzione

In C ++, è possibile definire operatori come + e -> per i tipi definiti dall'utente. Ad esempio, l'intestazione <string> definisce un operatore + per concatenare le stringhe. Questo viene fatto definendo una funzione dell'operatore usando la parola chiave operator .

Osservazioni

Gli operatori per i tipi built-in non possono essere modificati, gli operatori possono essere sovraccaricati solo per i tipi definiti dall'utente. Cioè, almeno uno degli operandi deve essere di un tipo definito dall'utente.

I seguenti operatori non possono essere sovraccaricati:

  • L'accesso membro o l'operatore "punto" .
  • Il puntatore all'operatore di accesso membro .*
  • L'operatore di risoluzione dell'oscilloscopio, ::
  • L'operatore condizionale ternario, ?:
  • dynamic_cast , static_cast , reinterpret_cast , const_cast , typeid , sizeof , alignof e noexcept
  • Le direttive di preelaborazione, # e ## , che vengono eseguite prima che qualsiasi tipo di informazione sia disponibile.

Ci sono alcuni operatori che non si dovrebbe (99,98% del tempo) di sovraccarico:

  • && e || (preferire, invece, usare la conversione implicita in bool )
  • ,
  • L'indirizzo-dell'operatore (unario & )

Perché? Perché sovraccaricano gli operatori che un altro programmatore non si aspetterebbe mai, comportando un comportamento diverso da quello previsto.

Ad esempio, l'utente ha definito && e || sovraccarichi di questi operatori perdono la loro valutazione di corto circuito e perdono le loro proprietà sequenziamento speciali (C ++ 17) , il numero di sequenza si applica anche a , overload dell'operatore.

Operatori aritmetici

Puoi sovraccaricare tutti gli operatori aritmetici di base:

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

Il sovraccarico per tutti gli operatori è lo stesso. Scorri verso il basso per la spiegazione

Sovraccarico al di fuori della 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;
}

Sovraccarico all'interno di 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;
}

Nota: l' operator+ dovrebbe tornare con un valore non const, in quanto restituire un riferimento non avrebbe senso (restituisce un nuovo oggetto) né restituirebbe un valore const (in genere non si dovrebbe restituirlo con const ). Il primo argomento è passato per valore, perché? Perché

  1. Non è possibile modificare l'oggetto originale ( Object foobar = foo + bar; non dovrebbe modificare foo dopo tutto, non avrebbe senso)
  2. Non puoi renderlo const , perché dovrai essere in grado di modificare l'oggetto (perché operator+ è implementato in termini di operator+= , che modifica l'oggetto)

Passare da const& sarebbe un'opzione, ma poi dovrai fare una copia temporanea dell'oggetto passato. Passando per valore, il compilatore lo fa per te.


operator+= restituisce un riferimento allo stesso, perché è quindi possibile incatenarli (non usare però la stessa variabile, sarebbe un comportamento indefinito a causa dei punti di sequenza).

Il primo argomento è un riferimento (vogliamo modificarlo), ma non const , perché non sarebbe possibile modificarlo. Il secondo argomento non dovrebbe essere modificato, e così per la ragione della prestazione viene passato da const& (passando per riferimento const è più veloce che per valore).

Operatori unari

Puoi sovraccaricare i 2 operatori unari:

  • ++foo e foo++
  • --foo e foo--

Il sovraccarico è uguale per entrambi i tipi ( ++ e -- ). Scorri verso il basso per la spiegazione

Sovraccarico al di fuori della 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;
}

Sovraccarico all'interno di 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;
}

Nota: l'operatore del prefisso restituisce un riferimento a se stesso, in modo da poter continuare le operazioni su di esso. Il primo argomento è un riferimento, poiché l'operatore del prefisso modifica l'oggetto, che è anche il motivo per cui non è const (non sarebbe possibile modificarlo in altro modo).


L'operatore postfisso restituisce di valore un valore temporaneo (il valore precedente) e quindi non può essere un riferimento, in quanto sarebbe un riferimento a un valore temporaneo, che sarebbe un valore immondizia alla fine della funzione, poiché la variabile temporanea si spegne di scopo). Inoltre non può essere const , perché dovresti essere in grado di modificarlo direttamente.

Il primo argomento è un riferimento non const all'oggetto "calling", perché se fosse const , non sarebbe possibile modificarlo e, se non fosse un riferimento, non si cambierebbe il valore originale.

È a causa della copia necessaria nei sovraccarichi dell'operatore postfisso che è meglio prendere l'abitudine di usare prefisso ++ anziché postfix ++ in cicli for . Dalla for prospettiva di ciclo, sono di solito funzionalmente equivalenti, ma ci potrebbe essere un leggero vantaggio delle prestazioni di utilizzare il prefisso ++, in particolare con le classi di "grasso" con un sacco di membri da copiare. Esempio di utilizzo del prefisso ++ in un ciclo for:

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

Operatori di confronto

Puoi sovraccaricare tutti gli operatori di confronto:

  • == e !=
  • > e <
  • >= e <=

Il modo consigliato per sovraccaricare tutti questi operatori è implementando solo 2 operatori ( == e < ) e quindi li usa per definire il resto. Scorri verso il basso per la spiegazione

Sovraccarico al di fuori della 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); }

Sovraccarico all'interno di 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); }

Ovviamente gli operatori restituiscono un bool , che indica true o false per l'operazione corrispondente.

Tutti gli operatori prendono i loro argomenti da const& , perché l'unica cosa che fanno gli operatori è il confronto, quindi non dovrebbero modificare gli oggetti. Passing By & (di riferimento) è più veloce di per valore, e per assicurarsi che gli operatori non modificano, si tratta di un const -Referenza.

Si noti che gli operatori all'interno della class / struct sono definiti come const , la ragione di ciò è che senza le funzioni const , il confronto degli oggetti const non sarebbe possibile, in quanto il compilatore non sa che gli operatori non modificano nulla.

Operatori di conversione

È possibile sovraccaricare gli operatori di tipo, in modo che il tipo possa essere convertito implicitamente nel tipo specificato.

L'operatore di conversione deve essere definito in una class / struct :

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

Nota: l'operatore è const per consentire la conversione di oggetti const .

Esempio:

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;

Operatore di sottoscrizione di matrice

È anche possibile sovraccaricare l'operatore di indice [] .

Dovresti sempre (il 99,98% delle volte) implementare 2 versioni, una versione const e una not const , perché se l'oggetto è const , non dovrebbe essere in grado di modificare l'oggetto restituito da [] .

Gli argomenti vengono passati da const& anziché da valore perché il passaggio per riferimento è più rapido che per valore e const modo che l'operatore non cambi accidentalmente l'indice.

Gli operatori restituiscono per riferimento, poiché per impostazione è possibile modificare l'oggetto [] return, ovvero:

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

Puoi sovraccaricare solo all'interno di una class / struct :

//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
}

Gli operatori di pedici multipli, [][]... , possono essere raggiunti tramite oggetti proxy. Il seguente esempio di una semplice classe matrice matrice principale lo dimostra:

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;
};

Operatore di chiamata di funzione

È possibile sovraccaricare l'operatore di chiamata di funzione () :

Il sovraccarico deve essere fatto all'interno di una 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, ...);

Per esempio:

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

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

Operatore di assegnazione

L'operatore di assegnazione è uno degli operatori più importanti perché consente di modificare lo stato di una variabile.

Se non sovraccarichi l'operatore dell'assegnazione per la tua class / struct , viene automaticamente generato dal compilatore: l'operatore di assegnazione generato automaticamente esegue un "assegnazione membro", cioè richiamando gli operatori di assegnazione su tutti i membri, in modo che un oggetto venga copiato all'altro, un membro alla volta. L'operatore di assegnazione dovrebbe essere sovraccarico quando la semplice assegnazione membro non è adatta per la class / struct , ad esempio se è necessario eseguire una copia profonda di un oggetto.

Sovraccarico dell'operatore di assegnazione = è facile, ma è necessario seguire alcuni semplici passaggi.

  1. Prova per l'auto-assegnazione. Questo controllo è importante per due motivi:
    • l'autoassegnazione è una copia inutile, quindi non ha senso eseguirla;
    • il prossimo passo non funzionerà nel caso di un autoassegnazione.
  2. Pulisci i vecchi dati. I vecchi dati devono essere sostituiti con quelli nuovi. Ora puoi comprendere la seconda ragione del passaggio precedente: se il contenuto dell'oggetto è stato distrutto, un'autoassegnazione non riuscirà a eseguire la copia.
  3. Copia tutti i membri. Se sovraccarichi l'operatore di assegnazione per la tua class o la tua struct , non viene generato automaticamente dal compilatore, quindi dovrai incaricarti di copiare tutti i membri dall'altro oggetto.
  4. Ritorna *this . L'operatore ritorna da solo per riferimento, poiché consente il concatenamento (cioè int b = (a = 6) + 4; //b == 10 ).
//T is some type
T& operator=(const T& other)
{
    //Do something (like copying values)
    return *this;
}

Nota: l' other viene passato da const& , poiché l'oggetto che si sta assegnando non deve essere modificato, e il passaggio per riferimento è più rapido del valore, e per essere sicuro che operator= non lo modifichi accidentalmente, è const .

L'operatore di assegnazione può solo essere sovraccaricato nella class / struct , perché il valore di sinistra = è sempre la class / struct stessa. Definirlo come una funzione libera non ha questa garanzia e non è consentito a causa di ciò.

Quando lo dichiarate nella class / struct , il valore di sinistra è implicitamente la class / struct stessa, quindi non ci sono problemi con questo.

Operatore NOT bit a bit

Il sovraccarico del NOT bit a bit ( ~ ) è abbastanza semplice. Scorri verso il basso per la spiegazione

Sovraccarico al di fuori della class / struct :

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

Sovraccarico all'interno di class / struct :

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

Nota: l' operator~ ritorna di valore, perché deve restituire un nuovo valore (il valore modificato) e non un riferimento al valore (sarebbe un riferimento all'oggetto temporaneo, che avrebbe un valore immondizia al suo interno non appena l'operatore è fatto). Non const neanche perché il codice chiamante dovrebbe essere in grado di modificarlo in seguito (cioè int a = ~a + 1; dovrebbe essere possibile).

All'interno della class / struct si deve fare un oggetto temporaneo, perché non è possibile modificare this , come sarebbe modificare l'oggetto originale, che non dovrebbe essere il caso.

Operatori di cambio di bit per I / O

Gli operatori << e >> sono comunemente usati come operatori "write" e "read":

  • std::ostream overload std::ostream << per scrivere variabili nel flusso sottostante (esempio: std::cout )
  • std::istream overload >> per leggere dal flusso sottostante a una variabile (esempio: std::cin )

Il modo in cui lo fanno è simile se si desidera sovraccaricarli "normalmente" al di fuori della class / struct , eccetto che specificando gli argomenti non sono dello stesso tipo:

  • Il tipo di std::ostream è il flusso da sovraccaricare (ad esempio, std::ostream ) passato per riferimento, per consentire il concatenamento (Concatenamento: std::cout << a << b; ). Esempio: std::ostream&
  • lhs sarebbe lo stesso del tipo di ritorno
  • rhs è il tipo da cui vuoi consentire l'overloading (cioè T ), passato da const& invece dal valore per il motivo delle prestazioni ( rhs non dovrebbe essere comunque modificato). Esempio: const Vector& .

Esempio:

//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;

Numeri complessi rivisitati

Il codice seguente implementa un tipo di numero complesso molto semplice per il quale il campo sottostante viene automaticamente promosso, seguendo le regole di promozione del tipo della lingua, in applicazione dei quattro operatori di base (+, -, * e /) con un membro di un campo diverso (sia esso un altro complex<T> o un tipo scalare).

Questo è inteso come un esempio olistico che copre il sovraccarico dell'operatore accanto all'uso di base dei modelli.

#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;
}

Operatori denominati

È possibile estendere C ++ con operatori denominati che sono "quotati" da operatori C ++ standard.

Per prima cosa iniziamo con una libreria dozzina di righe:

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) );
  }
}

questo non fa ancora niente

Innanzitutto, aggiungendo i vettori

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;

Il nucleo qui è che definiamo un oggetto append di tipo append_t:named_operator::make_operator<append_t> .

Quindi sovraccarichiamo named_invoke (lhs, append_t, rhs) per i tipi che vogliamo a destra e a sinistra.

La libreria sovraccarica lhs*append_t , restituendo un oggetto half_apply temporaneo. half_apply*rhs anche half_apply*rhs per chiamare named_invoke( lhs, append_t, rhs ) .

Dobbiamo semplicemente creare il token append_t appropriato e creare un named_invoke compatibile con ADL con la firma appropriata, e tutto si aggancia e funziona.

Per un esempio più complesso, si supponga di voler avere una moltiplicazione degli elementi di un array std :::

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])...
      }};
    });
  }
}

esempio dal vivo

Questo codice array element-element può essere esteso per lavorare su tuple o coppie o array in stile C, o anche contenitori di lunghezza variabile se si decide cosa fare se le lunghezze non corrispondono.

Si potrebbe anche digitare un tipo di operatore element-element e ottenere lhs *element_wise<'+'>* rhs .

Anche gli operatori di prodotto *dot* e *cross* sono ovvi usi.

L'uso di * può essere esteso per supportare altri delimitatori, come + . La precisione del delimitatore determina la precisione dell'operatore indicato, che può essere importante quando si traducono le equazioni fisiche in C ++ con l'uso minimo di extra () s.

Con una leggera modifica nella libreria sopra, possiamo supportare gli operatori ->*then* ed estendere la std::function prima che lo standard sia aggiornato, o scrivere monadic ->*bind* . Potrebbe anche avere un operatore con lo stato, in cui passiamo con attenzione l' Op alla funzione di invocazione finale, permettendo:

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);
};

generazione di un operatore denominato in allegato al contenitore in C ++ 17.



Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow