C++
Operatörens överbelastning
Sök…
Introduktion
I C ++ är det möjligt att definiera operatörer som +
och ->
för användardefinierade typer. Exempelvis definierar <string>
-huvudet en +
-operatör för att sammanfoga strängar. Detta görs genom att definiera en operatörsfunktion med operator
nyckelord .
Anmärkningar
Operatörerna för inbyggda typer kan inte ändras, operatörer kan bara överbelastas för användardefinierade typer. Det vill säga, åtminstone en av operandema måste vara av en användardefinierad typ.
Följande operatörer kan inte överbelastas:
- Medlemåtkomst eller "dot" -operatör
.
- Pekaren till medlemsåtkomstoperatören
.*
- Operatören för omfattningsupplösning,
::
- Den ternära villkorade operatören,
?:
-
dynamic_cast
,static_cast
,reinterpret_cast
,const_cast
,typeid
,sizeof
,alignof
ochnoexcept
- Förbehandlingsdirektiven,
#
och##
, som körs innan någon typ av information är tillgänglig.
Det finns några aktörer som du bör inte (99,98% av tiden) Överbelastning:
-
&&
och||
(föredrar istället implicit konvertering tillbool
) -
,
- Operatörens adress (unary
&
)
Varför? Eftersom de överbelastar operatörer som en annan programmerare aldrig kan förvänta sig, vilket resulterar i annat beteende än förväntat.
Till exempel användardefinierade &&
och ||
överbelastningar av dessa operatörer förlorar sin kortslutnings utvärdering och förlora sina speciella sekvenseringsegenskaper (C ++ 17) , sekvense frågan gäller även ,
operatörsöverbelastningar.
Aritmetiska operatörer
Du kan överbelasta alla grundläggande aritmetiska operatörer:
-
+
och+=
-
-
och-=
-
*
och*=
-
/
och/=
-
&
och&=
-
|
och|=
-
^
och^=
-
>>
och>>=
-
<<
och<<=
Överbelastning för alla operatörer är densamma. Rulla nedåt för att förklara
Överbelastning utanför 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;
}
Överbelastning inuti 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;
}
Obs: operator+
ska returnera med icke-const-värde, eftersom returnering av en referens inte skulle vara vettigt (det returnerar ett nytt objekt) och inte heller skulle returnera ett const
värde (du bör i allmänhet inte returnera med const
). Det första argumentet överförs av värde, varför? Eftersom
- Du kan inte ändra det ursprungliga objektet (
Object foobar = foo + bar;
borde inte ändrafoo
trots allt, det skulle inte vara meningsfullt) - Du kan inte göra det
const
, eftersom du måste kunna ändra objektet (eftersomoperator+
är implementerat i termer avoperator+=
, vilket modifierar objektet)
Att gå förbi const&
skulle vara ett alternativ, men då måste du göra en tillfällig kopia av det passerade objektet. Genom att gå förbi värde gör kompilatorn det åt dig.
operator+=
returnerar en referens till sig själv, eftersom det då är möjligt att kedja dem (använd dock inte samma variabel, det skulle vara odefinierat beteende på grund av sekvenspunkter).
Det första argumentet är en referens (vi vill ändra den), men inte const
, för då skulle du inte kunna ändra den. Det andra argumentet bör inte modifieras, och därför av prestanda skäl passeras av const&
(passering av const referens är snabbare än av värde).
Unary operatörer
Du kan överbelasta de två unary-operatörerna:
-
++foo
ochfoo++
-
--foo
ochfoo--
Överbelastning är densamma för båda typerna ( ++
och --
). Rulla nedåt för att förklara
Överbelastning utanför 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;
}
Överbelastning inuti 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;
}
Obs: Prefixoperatören returnerar en referens till sig själv, så att du kan fortsätta arbeta med den. Det första argumentet är en referens, eftersom prefixoperatören ändrar objektet, det är också anledningen till att det inte är const
(du skulle inte kunna ändra det på annat sätt).
Postfix-operatören returnerar med ett temporärt värde (det föregående värdet), och det kan därför inte vara en referens, eftersom det skulle vara en referens till en temporär, vilket skulle vara skräpvärde i slutet av funktionen, eftersom den temporära variabeln slocknar omfattning). Det kan inte heller vara const
eftersom du borde kunna ändra det direkt.
Det första argumentet är en icke- const
hänvisning till det "ringer" -objektet, för om det var const
, skulle du inte kunna ändra det, och om det inte var en referens, skulle du inte ändra det ursprungliga värdet.
Det är på grund av den kopiering som krävs för överbelastning av postfixoperatörerna att det är bättre att göra det till en vana att använda prefix ++ istället för postfix ++ for
slingor. Från for
loop-perspektivet är de vanligtvis funktionellt likvärdiga, men det kan vara en liten prestationsfördel att använda prefix ++, särskilt med "feta" klasser med många medlemmar att kopiera. Exempel på att använda prefix ++ i en för loop:
for (list<string>::const_iterator it = tokens.begin();
it != tokens.end();
++it) { // Don't use it++
...
}
Jämförelseoperatörer
Du kan överbelasta alla jämförande operatörer:
-
==
och!=
-
>
och<
-
>=
och<=
Det rekommenderade sättet att överbelasta alla dessa operatörer är genom att bara implementera två operatörer ( ==
och <
) och sedan använda dem för att definiera resten. Rulla nedåt för att förklara
Överbelastning utanför 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); }
Överbelastning inuti 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); }
Operatörerna returnerar uppenbarligen en bool
, vilket anger true
eller false
för motsvarande operation.
Alla operatörer tar sina argument från const&
, eftersom det enda som gör operatörerna är att jämföra, så de bör inte ändra objekten. Förbipasserande &
(referens) är snabbare än i värde, och att se till att operatörerna inte ändrar det, det är en const
-referens.
Observera att operatörerna i class
/ struct
definieras som const
, orsaken till detta är att utan att funktionerna är const
, skulle det inte vara möjligt att jämföra const
objekt, eftersom kompilatorn inte vet att operatörerna inte ändrar något.
Konverteringsoperatörer
Du kan överbelasta typoperatörer, så att din typ implicit kan konverteras till den angivna typen.
Konverteringsoperatören måste definieras i en class
/ struct
:
operator T() const { /* return something */ }
Obs: operatören är const
att tillåta const
objekt att konverteras.
Exempel:
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-abonnemangsoperatör
Du kan till och med överbelasta array-subscriptoperatören []
.
Du bör alltid (99,98% av tiden) implementera två versioner, en const
och en not- const
version, för om objektet är const
bör det inte kunna ändra objektet som returneras av []
.
Argumenten skickas av const&
istället för efter värde eftersom det går snabbare att gå än referensvärde, och const
så att operatören inte ändrar indexet av misstag.
Operatörerna återvänder med referens, eftersom du genom design kan ändra objektets []
retur, dvs:
std::vector<int> v{ 1 };
v[0] = 2; //Changes value of 1 to 2
//wouldn't be possible if not returned by reference
Du kan bara överbelasta i en 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
}
Flera abonnentoperatörer, [][]...
, kan uppnås via proxyobjekt. Följande exempel på en enkel rad-major matrisklass visar detta:
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;
};
Funktionssamtaloperatör
Du kan överbelasta operatören för funktionssamtal ()
:
Överbelastning måste göras inuti en 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, ...);
Till exempel:
struct Sum
{
int operator()(int a, int b)
{
return a + b;
}
};
//Create instance of struct
Sum sum;
int result = sum(1, 1); //result == 2
Uppdragsoperatör
Uppdragsoperatören är en av de viktigaste operatörerna eftersom den låter dig ändra status för en variabel.
Om du inte överbelaster tilldelningsoperatören för din class
/ struct
genereras den automatiskt av kompilatorn: den automatiskt genererade tilldelningsoperatören utför en "medlemsvis tilldelning", dvs genom att åkalla tilldelningsoperatörer på alla medlemmar, så att ett objekt kopieras till den andra, en medlem i tid. Uppdragsoperatören bör överbelastas när den enkla medlemsvisa tilldelningen inte är lämplig för din class
/ struct
, till exempel om du behöver utföra en djup kopia av ett objekt.
Överbelastning av tilldelningsoperatören =
är enkelt, men du bör följa några enkla steg.
- Test för självuppdrag. Denna kontroll är viktig av två skäl:
- en självuppgift är en onödig kopia, så det är inte vettigt att utföra det;
- nästa steg fungerar inte när det gäller en självuppdrag.
- Rengör gamla data. De gamla uppgifterna måste ersättas med nya. Nu kan du förstå det andra skälet till det föregående steget: om innehållet i objektet förstördes kommer en självuppgift inte att utföra kopian.
- Kopiera alla medlemmar. Om du överbelaster tilldelningsoperatören för din
class
eller dinstruct
genereras den inte automatiskt av kompilatorn, så du måste ta ansvar för att kopiera alla medlemmar från det andra objektet. - Returnera
*this
. Operatören återvänder av sig själv genom referens, eftersom den tillåter kedja (dvsint b = (a = 6) + 4; //b == 10
).
//T is some type
T& operator=(const T& other)
{
//Do something (like copying values)
return *this;
}
Obs: other
passeras av const&
, eftersom objektet som tilldelas inte bör ändras, och att passera genom referens är snabbare än med värde, och för att se till att operator=
inte ändrar det av misstag är det const
.
Uppdragsoperatören kan bara överbelastas i class
/ struct
, eftersom det vänstra värdet på =
alltid är class
/ struct
själv. Att definiera den som en fri funktion har inte den här garantin och tillåts därför inte.
När du förklarar det i class
/ struct
är det vänstra värdet implicit själva class
/ struct
, så det finns inga problem med det.
Bitvis INTE operatör
Överbelastning av bitvis INTE ( ~
) är ganska enkelt. Rulla nedåt för att förklara
Överbelastning utanför class
/ struct
:
T operator~(T lhs)
{
//Do operation
return lhs;
}
Överbelastning inuti class
/ struct
:
T operator~()
{
T t(*this);
//Do operation
return t;
}
Obs: operator~
returnerar med värde, eftersom den måste returnera ett nytt värde (det modifierade värdet) och inte en referens till värdet (det skulle vara en referens till det temporära objektet, som skulle ha skräpvärde i det så snart som operatören är klar). Inte const
antingen därför den anropande koden bör kunna ändra den efteråt (dvs int a = ~a + 1;
bör vara möjligt).
Inne i class
/ struct
måste du skapa ett tillfälligt objekt, eftersom du inte kan ändra this
, eftersom det skulle modifiera det ursprungliga objektet, vilket inte borde vara fallet.
Bitskiftoperatörer för I / O
Operatörerna <<
och >>
används ofta som "skriva" och "läs" operatörer:
-
std::ostream
överbelastningar<<
att skriva variabler till den underliggande strömmen (exempel:std::cout
) -
std::istream
överbelastas>>
att läsa från den underliggande strömmen till en variabel (exempel:std::cin
)
Hur de gör detta är liknande om du vill överbelasta dem "normalt" utanför class
/ struct
, förutom att specificera argumenten inte är av samma typ:
- Returtyp är den ström som du vill överbelasta från (till exempel
std::ostream
) som skickas genom referens för att tillåta kedja (Chaining:std::cout << a << b;
). Exempel:std::ostream&
-
lhs
skulle vara samma som returtypen -
rhs
är den typ du vill tillåta överbelastning från (dvsT
), passerad avconst&
istället för värde av prestandaskäl (rhs
bör inte ändras ändå). Exempel:const Vector&
.
Exempel:
//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;
Komplexa nummer reviderade
Koden nedan implementerar en mycket enkel komplex numretyp för vilken det underliggande fältet automatiskt marknadsförs, enligt språkets typpromoteringsregler, under tillämpning av de fyra grundläggande operatörerna (+, -, * och /) med en medlem av ett annat fält (vare sig det är en annan complex<T>
eller någon skalartyp).
Detta är avsett att vara ett holistiskt exempel som täcker överbelastning av operatörer tillsammans med grundläggande användning av mallar.
#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;
}
Namngivna operatörer
Du kan utöka C ++ med namngivna operatörer som "citeras" av vanliga C ++ -operatörer.
Först börjar vi med ett dussin-linjebibliotek:
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) );
}
}
det här gör ingenting ännu.
Först bifogade vektorer
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;
Kärnan här är att vi definierar ett append
objekt av typen append_t:named_operator::make_operator<append_t>
.
Vi överbelasta sedan namnet_invoke (lhs, append_t, rhs) för de typer vi vill ha till höger och vänster.
Biblioteket överbelaster lhs*append_t
och returnerar ett tillfälligt half_apply
objekt. Det överbelaster också half_apply*rhs
att kalla named_invoke( lhs, append_t, rhs )
.
Vi måste helt enkelt skapa rätt append_t
token och göra ett ADL-vänligt named_invoke
med rätt signatur, och allt kopplas upp och fungerar.
För ett mer komplext exempel, anta att du vill ha elementvis multiplikation av element i en 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])...
}};
});
}
}
Denna elementmässiga matriskod kan utvidgas till att fungera på tuples eller par eller matriser i C-stil, eller till och med behållare med variabel längd om du bestämmer vad du ska göra om längderna inte matchar.
Du kan också vara en elementvis operatörstyp och få lhs *element_wise<'+'>* rhs
.
Att skriva en *dot*
och *cross*
produktoperatör är också uppenbara användningar.
Användningen av *
kan utvidgas till att stödja andra avgränsare, som +
. Avgränsningen av delometret bestämmer förekomsten för den nämnda operatören, vilket kan vara viktigt vid översättning av fysikekvationer till C ++ med minimal användning av extra ()
s.
Med en liten förändring i biblioteket ovan kan vi stödja ->*then*
operatörer och utöka std::function
innan standarden uppdateras, eller skriva monadisk ->*bind*
. Det kan också ha en tillståndsgivande operatör, där vi försiktigt överför Op
till den slutliga åkallande funktionen, vilket tillåter:
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);
};
generera en namngiven behållare-tilläggsoperatör i C ++ 17.