C++
Sovraccarico dell'operatore
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
enoexcept
- 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 inbool
) -
,
- 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é
- Non è possibile modificare l'oggetto originale (
Object foobar = foo + bar;
non dovrebbe modificarefoo
dopo tutto, non avrebbe senso) - Non puoi renderlo
const
, perché dovrai essere in grado di modificare l'oggetto (perchéoperator+
è implementato in termini dioperator+=
, 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
efoo++
-
--foo
efoo--
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.
- 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.
- 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.
- Copia tutti i membri. Se sovraccarichi l'operatore di assegnazione per la tua
class
o la tuastruct
, non viene generato automaticamente dal compilatore, quindi dovrai incaricarti di copiare tutti i membri dall'altro oggetto. - 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
overloadstd::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 daconst&
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])...
}};
});
}
}
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.