C++
Przeciążenie operatora
Szukaj…
Wprowadzenie
W C ++ można zdefiniować operatory takie jak +
i ->
dla typów zdefiniowanych przez użytkownika. Na przykład nagłówek <string>
definiuje operator +
do łączenia łańcuchów. Odbywa się to poprzez zdefiniowanie funkcji operatora za pomocą słowa kluczowego operator
.
Uwagi
Operatorów dla wbudowanych typów nie można zmienić, operatory mogą być przeciążone tylko dla typów zdefiniowanych przez użytkownika. Oznacza to, że co najmniej jeden operand musi być typu zdefiniowanego przez użytkownika.
Następujące operatory nie mogą być przeciążone:
- Dostęp członka lub operator „kropkowy”
.
- Wskaźnik do operatora dostępu do elementu
.*
- Operator rozdzielczości zakresu
::
- Trójskładnikowy operator warunkowy
?:
-
dynamic_cast
,static_cast
,reinterpret_cast
,const_cast
,typeid
,sizeof
,alignof
inoexcept
- Dyrektywy przetwarzania wstępnego
#
i##
, które są wykonywane przed udostępnieniem jakichkolwiek informacji o typie.
Istnieje kilka operatorów, których nie powinieneś (99,98% czasu) przeciążać:
- i
&&
i||
(wolę zamiast tego użyć domyślnej konwersji nabool
) -
,
- Adres operatora (jednostkowy
&
)
Dlaczego? Ponieważ przeciążają one operatorów, których inny programista może się nigdy nie spodziewać, powodując inne zachowanie niż oczekiwano.
Na przykład użytkownik zdefiniował &&
i ||
przeciążenia tych operatorów tracą swoją ocenę zwarciem i tracą swoje szczególne właściwości sekwencjonowania (C ++ 17) , problem dotyczy również sekwencjonowania ,
przeciążenia operatora.
Operatory arytmetyczne
Możesz przeciążać wszystkie podstawowe operatory arytmetyczne:
-
+
i+=
-
-
i-=
-
*
i*=
-
/
i/=
-
&
i&=
-
|
i|=
-
^
i^=
-
>>
i>>=
-
<<
i<<=
Przeciążenie dla wszystkich operatorów jest takie samo. Przewiń w dół, aby uzyskać wyjaśnienie
Przeciążenie poza 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;
}
Przeciążenie wewnątrz 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;
}
Uwaga: operator+
powinien zwracać wartość inną niż const, ponieważ zwracanie odwołania nie miałoby sensu (zwraca nowy obiekt) ani nie zwraca wartości const
(generalnie nie powinieneś zwracać const
). Pierwszy argument przekazywany jest przez wartość, dlaczego? Bo
- Nie możesz modyfikować oryginalnego obiektu (
Object foobar = foo + bar;
końcu nie powinieneś modyfikowaćfoo
, to nie miałoby sensu) - Nie możesz ustawić go jako
const
, ponieważ będziesz musiał móc modyfikować obiekt (ponieważoperator+
jest zaimplementowany w kategoriachoperator+=
, który modyfikuje obiekt)
Przekazywanie przez const&
byłoby opcją, ale wtedy musisz wykonać tymczasową kopię przekazywanego obiektu. Przekazując wartość, kompilator robi to za Ciebie.
operator+=
zwraca odwołanie do siebie, ponieważ można je następnie połączyć w łańcuch (nie należy jednak używać tej samej zmiennej, co byłoby niezdefiniowanym zachowaniem ze względu na punkty sekwencji).
Pierwszy argument jest odwołaniem (chcemy go zmodyfikować), ale nie const
, ponieważ wtedy nie byłbyś w stanie go zmodyfikować. Drugi argument nie powinien być modyfikowany, więc ze względu na wydajność jest przekazywany przez const&
(przekazywanie przez const odniesienia jest szybsze niż według wartości).
Unary operatorzy
Możesz przeciążić 2 jednoargumentowych operatorów:
-
++foo
ifoo++
-
--foo
ifoo--
Przeciążenie jest takie samo dla obu typów ( ++
i --
). Przewiń w dół, aby uzyskać wyjaśnienie
Przeciążenie poza 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;
}
Przeciążenie wewnątrz 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;
}
Uwaga: Operator prefiksu zwraca odwołanie do siebie, abyś mógł kontynuować operacje na nim. Pierwszy argument jest odwołaniem, ponieważ operator prefiksu zmienia obiekt, dlatego też nie jest on const
(inaczej nie byłbyś w stanie go zmodyfikować).
Operator Postfiks zwraca wartość tymczasową (poprzednia wartość), więc nie może być referencją, ponieważ byłaby referencją do tymczasowej, która byłaby wartością śmieciową na końcu funkcji, ponieważ zmienna tymczasowa gaśnie zakresu). Nie może być również const
, ponieważ powinieneś być w stanie bezpośrednio go modyfikować.
Pierwszy argument jest const
odwołaniem do obiektu „wywołującego”, ponieważ gdyby był to const
, nie byłbyś w stanie go zmodyfikować, a gdyby nie było odwołaniem, nie zmieniłbyś oryginalnej wartości.
To właśnie z powodu kopiowania potrzebne przeciążenia operatora postfix, że lepiej, aby to nawyk korzystania z prefiks ++ zamiast postfix ++ w for
pętli. Z perspektywy pętli for
są one zazwyczaj funkcjonalnie równoważne, ale może być niewielka przewaga wydajności przy użyciu prefiksu ++, szczególnie w przypadku klas „grubych” z dużą liczbą elementów do skopiowania. Przykład użycia przedrostka ++ w pętli for:
for (list<string>::const_iterator it = tokens.begin();
it != tokens.end();
++it) { // Don't use it++
...
}
Operatory porównania
Możesz przeciążać wszystkie operatory porównania:
-
==
i!=
-
>
i<
-
>=
i<=
Zalecanym sposobem na przeciążenie wszystkich tych operatorów jest zaimplementowanie tylko 2 operatorów ( ==
i <
), a następnie użycie ich do zdefiniowania pozostałych. Przewiń w dół, aby uzyskać wyjaśnienie
Przeciążenie poza 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); }
Przeciążenie wewnątrz 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); }
Operatory oczywiście zwracają wartość bool
, wskazując wartość true
lub false
dla odpowiedniej operacji.
Wszyscy operatorzy biorą swoje argumenty za pomocą const&
, ponieważ jedyne, co robią operatorzy, to porównywanie, więc nie powinni modyfikować obiektów. Przechodząc przez &
(odniesienia) jest szybsza niż przez wartość, i upewnić się, że operatorzy nie modyfikują go, jest const
-reference.
Zauważ, że operatory wewnątrz class
/ struct
są zdefiniowane jako const
, ponieważ bez funkcji będących const
, porównywanie obiektów const
nie byłoby możliwe, ponieważ kompilator nie wie, że operatorzy niczego nie modyfikują.
Operatory konwersji
Możesz przeciążać operatory typu, aby Twój typ mógł zostać niejawnie przekonwertowany na określony typ.
Operator konwersji musi być zdefiniowany w class
/ struct
:
operator T() const { /* return something */ }
Uwaga: operator jest const
aby umożliwić konwersję obiektów const
.
Przykład:
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;
Operator tablicy indeksów
Możesz nawet przeciążać operatora indeksu tablicy []
.
Należy zawsze (99,98% czasu) wdrożyć 2 wersje, a const
i not- const
wersję, bo jeśli obiekt jest const
, to nie powinno być w stanie zmodyfikować obiekt zwrócony przez []
.
Argumenty są przekazywane przez const&
zamiast przez wartość, ponieważ przekazywanie przez referencję jest szybsze niż przez wartość i const
dzięki czemu operator nie zmienia przypadkowo indeksu.
Operatory zwracają przez odniesienie, ponieważ zgodnie z projektem można zmodyfikować zwracany obiekt []
, tj .:
std::vector<int> v{ 1 };
v[0] = 2; //Changes value of 1 to 2
//wouldn't be possible if not returned by reference
Możesz przeciążać tylko wewnątrz 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
}
Wiele operatorów indeksu dolnego [][]...
można uzyskać za pomocą obiektów proxy. Poniższy przykład prostej klasy macierzy z głównym wierszem pokazuje:
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;
};
Operator wywołania funkcji
Możesz przeładować funkcję wywołania operatora ()
:
Przeciążenie musi być wykonane wewnątrz 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, ...);
Na przykład:
struct Sum
{
int operator()(int a, int b)
{
return a + b;
}
};
//Create instance of struct
Sum sum;
int result = sum(1, 1); //result == 2
Operator przypisania
Operator przypisania jest jednym z najważniejszych operatorów, ponieważ umożliwia zmianę statusu zmiennej.
Jeśli nie przeładujesz operatora przypisania dla swojej class
/ struct
, zostanie on automatycznie wygenerowany przez kompilator: automatycznie wygenerowany operator przypisania wykonuje „przypisanie członkostwa”, tj. Przez wywołanie operatorów przypisania na wszystkich elementach, dzięki czemu jeden obiekt jest kopiowany do drugiego, członka na czas. Operator przypisania powinien być przeciążony, gdy proste przypisanie członkowe nie jest odpowiednie dla twojej class
/ struct
, na przykład, jeśli chcesz wykonać głęboką kopię obiektu.
Przeładowanie operatora przypisania =
jest łatwe, ale powinieneś wykonać kilka prostych kroków.
- Test na samodzielne przydzielenie. Ta kontrola jest ważna z dwóch powodów:
- samodzielne zadanie jest niepotrzebną kopią, więc nie ma sensu go wykonywać;
- następny krok nie zadziała w przypadku samodzielnego przydzielenia.
- Wyczyść stare dane. Stare dane należy zastąpić nowymi. Teraz możesz zrozumieć drugi powód poprzedniego kroku: jeśli zawartość obiektu zostanie zniszczona, wykonanie zadania kopiowania nie powiedzie się.
- Skopiuj wszystkich członków. Jeśli przeciążymy operatora przypisania dla swojej
class
lubstruct
, nie zostanie on automatycznie wygenerowany przez kompilator, więc będziesz musiał przejąć kontrolę nad kopiowaniem wszystkich elementów z innego obiektu. - Zwróć
*this
. Operator powraca sam przez odniesienie, ponieważ pozwala na tworzenie łańcuchów (tj.int b = (a = 6) + 4; //b == 10
).
//T is some type
T& operator=(const T& other)
{
//Do something (like copying values)
return *this;
}
Uwaga: other
są przekazywane przez const&
, ponieważ przypisanego obiektu nie należy zmieniać, a przekazywanie przez referencję jest szybsze niż według wartości, a aby upewnić się, że operator=
nie zmodyfikuje go przypadkowo, jest to const
.
Operatorowi przypisanie tylko może być przeładowany do class
/ struct
, ponieważ lewa wartość =
zawsze jest class
/ struct
się. Zdefiniowanie go jako funkcji bezpłatnej nie ma tej gwarancji i dlatego jest niedozwolone.
Kiedy deklarujesz to w class
/ struct
, lewa wartość jest domyślnie samą class
/ struct
, więc nie ma z tym problemu.
Bitowy operator NOT
Przeciążenie bitowe NOT ( ~
) jest dość proste. Przewiń w dół, aby uzyskać wyjaśnienie
Przeciążenie poza class
/ struct
:
T operator~(T lhs)
{
//Do operation
return lhs;
}
Przeciążenie wewnątrz class
/ struct
:
T operator~()
{
T t(*this);
//Do operation
return t;
}
Uwaga: operator~
zwraca wartość, ponieważ musi zwrócić nową wartość (wartość zmodyfikowaną), a nie odwołanie do wartości (byłoby to odwołanie do obiektu tymczasowego, który miałby w nim wartość śmieciową, gdy tylko operator jest skończony). Nie const
albo dlatego, kod wywołujący powinien móc go zmodyfikować później (tj int a = ~a + 1;
powinno być możliwe).
Wewnątrz class
/ struct
trzeba zrobić tymczasowy obiekt, ponieważ nie można modyfikować this
, jak byłoby zmodyfikować oryginalny obiekt, który nie powinien być przypadek.
Operatory przesunięcia bitów dla I / O
Operatory <<
i >>
są powszechnie używane jako operatory „zapisu” i „odczytu”:
- przeciążenia
std::ostream
<<
aby zapisać zmienne do strumienia bazowego (przykład:std::cout
) -
std::istream
overloads>>
aby odczytać z podstawowego strumienia do zmiennej (przykład:std::cin
)
Sposób, w jaki to robią, jest podobny, jeśli chcesz je przeciążyć „normalnie” poza class
/ struct
, z tym wyjątkiem, że podanie argumentów nie jest tego samego typu:
- Typ zwracany to strumień, z którego chcesz przeciążać (na przykład
std::ostream
) przekazywany przez referencję, aby umożliwić tworzenie łańcuchów (Chaining:std::cout << a << b;
). Przykład:std::ostream&
-
lhs
będzie taki sam jak typ zwrotu -
rhs
jest typem, dla którego chcesz pozwolić na przeładowanie (tj.T
), przekazane przezconst&
zamiast wartości ze względu na wydajność (rhs
tak nie powinien być zmieniany). Przykład:const Vector&
.
Przykład:
//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;
Ponownie odwiedzone liczby zespolone
Poniższy kod implementuje bardzo prosty złożony typ liczbowy, dla którego pole podstawowe jest automatycznie promowane, zgodnie z zasadami promocji typu języka, przy zastosowaniu czterech podstawowych operatorów (+, -, * i /) z członkiem innego pola (może to być kolejny complex<T>
lub jakiś typ skalarny).
Ma to być holistyczny przykład obejmujący przeciążanie operatora wraz z podstawowym użyciem szablonów.
#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;
}
Nazwani operatorzy
Możesz rozszerzyć C ++ o nazwane operatory, które są „cytowane” przez standardowe operatory C ++.
Najpierw zaczynamy od biblioteki kilkunastu wierszy:
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) );
}
}
to jeszcze nic nie robi.
Po pierwsze, dołączając wektory
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;
Rdzeniem tutaj jest to, że definiujemy obiekt append
typu append_t:named_operator::make_operator<append_t>
.
Następnie przeciążamy named_invoke (lhs, append_t, rhs) dla typów, które chcemy po prawej i lewej stronie.
Biblioteka przeciąża lhs*append_t
, zwracając tymczasowy obiekt half_apply
. Przeciąża również half_apply*rhs
aby wywołać named_invoke( lhs, append_t, rhs )
.
Musimy po prostu stworzyć odpowiedni token append_t
i wykonać przyjazny dla ADL named_invoke
odpowiedniego podpisu, a wszystko się append_t
i named_invoke
.
Dla bardziej złożonego przykładu, załóżmy, że chcesz mieć mnożenie elementów elementu std :: array:
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])...
}};
});
}
}
Ten elementowy kod tablicy można rozszerzyć, aby działał na krotkach lub parach lub tablicach typu C, a nawet pojemnikach o zmiennej długości, jeśli zdecydujesz, co zrobić, jeśli długości nie będą pasować.
Możesz także wpisać operator typu element i uzyskać lhs *element_wise<'+'>* rhs
.
Pisanie *dot*
i *cross*
operatorów produktów jest również oczywistym zastosowaniem.
Użycie *
można rozszerzyć w celu obsługi innych ograniczników, takich jak +
. Częstotliwość delimetrowa określa częstość występowania nazwanego operatora, co może być ważne przy tłumaczeniu równań fizyki na C ++ przy minimalnym użyciu dodatkowych ()
.
Z niewielką zmianą w powyższej bibliotece możemy obsługiwać ->*then*
operatorów i rozszerzać std::function
przed aktualizacją standardu, lub pisać monadyczne ->*bind*
. Może również mieć stanowego operatora o nazwie, w którym ostrożnie przekazujemy Op
do ostatecznej funkcji invoke, umożliwiając:
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);
};
generowanie nazwanego operatora dołączającego kontener w C ++ 17.