Zoeken…


Invoering

In C ++ is het mogelijk om operatoren zoals + en -> te definiëren voor door de gebruiker gedefinieerde typen. De kop <string> definieert bijvoorbeeld een operator + om tekenreeksen samen te voegen. Dit wordt gedaan door een bediener functie met de operator trefwoord .

Opmerkingen

De operators voor ingebouwde typen kunnen niet worden gewijzigd, operators kunnen alleen worden overbelast voor door de gebruiker gedefinieerde typen. Dat wil zeggen dat ten minste een van de operanden van het door de gebruiker gedefinieerde type moet zijn.

De volgende operators kunnen niet worden overbelast:

  • De operator voor lidtoegang of "stip" .
  • De aanwijzer naar de operator voor toegang tot leden .*
  • De scope-resolutie-operator, ::
  • De ternaire voorwaardelijke operator, ?:
  • dynamic_cast , static_cast , reinterpret_cast , const_cast , typeid , sizeof , alignof en noexcept
  • De voorverwerkingsrichtlijnen, # en ## , die worden uitgevoerd voordat er type-informatie beschikbaar is.

Er zijn een aantal exploitanten die moet je niet (99,98% van de tijd) overbelasten:

  • && en || (gebruik in plaats daarvan liever impliciete conversie naar bool )
  • ,
  • Het adres van de operator (unary & )

Waarom? Omdat ze operators overbelasten die een andere programmeur misschien nooit zou verwachten, wat resulteert in ander gedrag dan verwacht.

De gebruiker heeft bijvoorbeeld && en || gedefinieerd overbelasting van deze operatoren verliezen hun kortsluiting evaluatie en verliezen hun speciale sequencing eigenschappen (C ++ 17) , de sequencing kwestie is ook van toepassing , operator overbelasting.

Rekenkundige operatoren

U kunt alle standaard rekenkundige operatoren overbelasten:

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

Overbelasting voor alle operators is hetzelfde. Scroll naar beneden voor uitleg

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

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

Opmerking: operator+ moet op niet-const-waarde terugkomen, omdat het retourneren van een verwijzing niet zinvol is (het retourneert een nieuw object) en evenmin op het teruggeven van een const waarde (u moet in het algemeen niet op const terugkeren). Het eerste argument wordt door waarde gegeven, waarom? Omdat

  1. Je kunt het originele object niet wijzigen ( Object foobar = foo + bar; zou foo toch niet moeten wijzigen, het zou niet logisch zijn)
  2. Je kunt het niet maken const , want je moet in staat zijn om het object te wijzigen (omdat operator+ is in termen van geïmplementeerde operator+= , waarin het object wijzigt)

Het passeren van const& zou een optie zijn, maar dan moet u een tijdelijke kopie maken van het doorgegeven object. Door waarde door te geven, doet de compiler het voor u.


operator+= retourneert een verwijzing naar zichzelf, omdat het dan mogelijk is om ze te koppelen (gebruik echter niet dezelfde variabele, dat zou ongedefinieerd gedrag zijn vanwege opeenvolgende punten).

Het eerste argument is een referentie (we willen het wijzigen), maar niet const , omdat je het dan niet zou kunnen wijzigen. Het tweede argument mag niet worden gewijzigd, en wordt om prestatieredenen doorgegeven door const& (doorgeven van constreferentie is sneller dan door waarde).

Unaire operatoren

U kunt de 2 unaire operators overbelasten:

  • ++foo en foo++
  • --foo en foo--

Overbelasting is hetzelfde voor beide typen ( ++ en -- ). Scroll naar beneden voor uitleg

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

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

Opmerking: de prefix-operator retourneert een verwijzing naar zichzelf, zodat u hierop kunt doorgaan. Het eerste argument is een verwijzing, omdat de prefixoperator het object wijzigt, dat is ook de reden waarom het geen const (anders zou u het niet kunnen wijzigen).


De postfix-operator retourneert per waarde een tijdelijke (de vorige waarde), en dus kan het geen verwijzing zijn, omdat het een verwijzing naar een tijdelijke, die vuilniswaarde aan het einde van de functie zou zijn, is omdat de tijdelijke variabele uitgaat van het toepassingsgebied). Het kan ook geen const , omdat u het rechtstreeks moet kunnen wijzigen.

Het eerste argument is een niet- const verwijzing naar het "roepende" object, want als het const zou zijn, zou u het niet kunnen wijzigen, en als het geen referentie zou zijn, zou u de oorspronkelijke waarde niet veranderen.

Het is vanwege het kopiëren dat nodig is in de overbelastingen van de postfix-operator, dat het beter is om er een gewoonte van te maken om prefix ++ te gebruiken in plaats van postfix ++ for lussen. Vanuit het for loop-perspectief zijn ze meestal functioneel equivalent, maar het gebruik van het voorvoegsel ++ kan een klein prestatievoordeel hebben, vooral bij "dikke" klassen met veel leden om te kopiëren. Voorbeeld van het gebruik van het voorvoegsel ++ in een for-lus:

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

Vergelijkingsoperatoren

U kunt alle vergelijkingsoperatoren overbelasten:

  • == en !=
  • > en <
  • >= en <=

De aanbevolen manier om al die operators te overbelasten is door slechts 2 operators ( == en < ) te implementeren en deze vervolgens te gebruiken om de rest te definiëren. Scroll naar beneden voor uitleg

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

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

De operatoren geven duidelijk een bool , met vermelding van true of false voor de bijbehorende bewerking.

Alle operatoren nemen hun argumenten op const& , omdat het enige dat operatoren doen vergelijken is, dus ze moeten de objecten niet wijzigen. Het passeren van & (referentie) is sneller dan op waarde, en om ervoor te zorgen dat de operatoren het niet wijzigen, is het een const -referentie.

Merk op dat de operatoren binnen de class / struct zijn gedefinieerd als const , de reden hiervoor is dat zonder const functies const objecten vergelijken niet mogelijk zou zijn, omdat de compiler niet weet dat de operatoren niets wijzigen.

Conversie-operators

U kunt type-operators overbelasten, zodat uw type impliciet kan worden geconverteerd naar het opgegeven type.

De conversie-operator moet worden gedefinieerd in een class / struct :

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

Opmerking: de operator is const zodat const objecten kunnen worden geconverteerd.

Voorbeeld:

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 subscript-operator

U kunt de array-subscript-operator [] zelfs overbelasten.

U moet altijd (99,98% van de tijd) 2 versies implementeren, een const en een niet- const versie, omdat als het object const , het niet in staat zou moeten zijn om het object te wijzigen dat wordt geretourneerd door [] .

De argumenten worden doorgegeven door const& plaats van door waarde, omdat het doorgeven door verwijzing sneller gaat dan door waarde, en const zodat de operator de index niet per ongeluk wijzigt.

De operatoren keren terug door verwijzing, omdat u door ontwerp het object [] retour kunt wijzigen, dwz:

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

Je kunt alleen binnen een class / struct overbelasten:

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

Meerdere subscriptoperators, [][]... , kunnen worden bereikt via proxyobjecten. Het volgende voorbeeld van een eenvoudige rij-hoofdmatrixklasse laat dit zien:

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

Functie oproepoperator

U kunt de functieoproepoperator () overbelasten:

Overbelasting moet gebeuren binnen een 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, ...);

Bijvoorbeeld:

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

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

Opdracht operator

De toewijzingsoperator is een van de belangrijkste operatoren omdat u hiermee de status van een variabele kunt wijzigen.

Als u de toewijzingsoperator voor uw class / struct niet overbelast, wordt deze automatisch gegenereerd door de compiler: de automatisch gegenereerde toewijzingsoperator voert een "op leden gebaseerde toewijzing" uit, dwz door toewijzingsoperators op alle leden aan te roepen, zodat één object wordt gekopieerd aan de ander, een lid tegelijk. De toewijzingsoperator moet worden overbelast wanneer de eenvoudige toewijzing voor leden niet geschikt is voor uw class / struct , bijvoorbeeld als u een diepe kopie van een object moet uitvoeren.

De toewijzingsoperator overbelasten = is eenvoudig, maar u moet enkele eenvoudige stappen volgen.

  1. Test voor zelftoekenning. Deze controle is om twee redenen belangrijk:
    • een zelfopdracht is een onnodige kopie, dus het heeft geen zin om het uit te voeren;
    • de volgende stap zal niet werken in het geval van een zelf-toewijzing.
  2. Reinig de oude gegevens. De oude gegevens moeten worden vervangen door nieuwe. Nu kunt u de tweede reden van de vorige stap begrijpen: als de inhoud van het object is vernietigd, kan een zelftoekenning de kopie niet uitvoeren.
  3. Kopieer alle leden. Als u de toewijzingsoperator voor uw class of uw struct overbelast, wordt deze niet automatisch gegenereerd door de compiler, dus moet u de leiding nemen over het kopiëren van alle leden van het andere object.
  4. Geef *this terug . De operator keert vanzelf terug door middel van verwijzing, omdat deze chaining mogelijk maakt (dwz int b = (a = 6) + 4; //b == 10 ).
//T is some type
T& operator=(const T& other)
{
    //Do something (like copying values)
    return *this;
}

Opmerking: other wordt doorgegeven door const& , omdat het toegewezen object niet moet worden gewijzigd en het doorgeven door verwijzing sneller is dan door waarde, en om ervoor te zorgen dat operator= het niet per ongeluk wijzigt, is het const .

De toewijzingsoperator kan alleen worden overbelast in de class / struct , omdat de linkerwaarde van = altijd de class / struct zelf is. Het definiëren van een gratis functie heeft deze garantie niet en is daarom niet toegestaan.

Wanneer u het in de class / struct declareert, is de linkerwaarde impliciet de class / struct zelf, dus daar is geen probleem mee.

Bitwise NIET-operator

De bitwise NOT ( ~ ) overbelasten is vrij eenvoudig. Scroll naar beneden voor uitleg

Overbelasting buiten class / struct :

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

Overbelasting binnen class / struct :

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

Opmerking: operator~ retourneert op waarde, omdat deze een nieuwe waarde (de gewijzigde waarde) moet retourneren, en geen verwijzing naar de waarde (het zou een verwijzing zijn naar het tijdelijke object, dat vuilniswaarde zou bevatten zodra de waarde de operator is klaar). Ook niet const omdat de aanroepcode deze achteraf zou moeten kunnen wijzigen (dwz int a = ~a + 1; zou mogelijk moeten zijn).

Binnen in de class / struct moet u een tijdelijk object te maken, want je kunt niet wijzigen this , omdat het het oorspronkelijke object, wat niet het geval zou moeten zijn zou wijzigen.

Bit shift-operators voor I / O

De operatoren << en >> worden gewoonlijk gebruikt als "schrijven" en "lezen" operatoren:

  • std::ostream overbelastingen << om variabelen naar de onderliggende stream te schrijven (voorbeeld: std::cout )
  • std::istream overbelastingen >> om van de onderliggende stream naar een variabele te lezen (voorbeeld: std::cin )

De manier waarop ze dit doen is vergelijkbaar als je ze "normaal" buiten de class / struct wilt overbelasten, behalve dat het specificeren van de argumenten niet van hetzelfde type is:

  • Retourtype is de stream waarvan u wilt overbelasten (bijvoorbeeld std::ostream ) die door referentie is doorgegeven om chaining mogelijk te maken (Chaining: std::cout << a << b; ). Voorbeeld: std::ostream&
  • lhs zou hetzelfde zijn als het retourtype
  • rhs is het type waarvan u overbelasting wilt toestaan (bijv. T ), doorgegeven door const& plaats van waarde om prestatieredenen ( rhs moet sowieso niet worden gewijzigd). Voorbeeld: const Vector& .

Voorbeeld:

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

Complexe nummers opnieuw bezocht

De onderstaande code implementeert een zeer eenvoudig complex nummertype waarvoor het onderliggende veld automatisch wordt gepromoot, in overeenstemming met de type promotieregels van de taal, onder toepassing van de vier basisoperatoren (+, -, * en /) met een lid van een ander veld (zij het een ander complex<T> of een scalair type).

Dit is bedoeld als een holistisch voorbeeld voor de overbelasting van de operator naast het basisgebruik van sjablonen.

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

Benoemde operatoren

U kunt C ++ uitbreiden met benoemde operatoren die worden "geciteerd" door standaard C ++ - operatoren.

Eerst beginnen we met een dozijn-regel bibliotheek:

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

dit doet nog niets.

Eerst het toevoegen van vectoren

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;

De kern hier is dat we een append object van het type append_t:named_operator::make_operator<append_t> .

Vervolgens overladen we names_invoke (lhs, append_t, rhs) voor de typen die we rechts en links willen.

De bibliotheek overbelast lhs*append_t en retourneert een tijdelijk half_apply object. Het overbelast ook half_apply*rhs om named_invoke( lhs, append_t, rhs ) aan te roepen.

We moeten gewoon het juiste append_t token maken en een ADL-vriendelijke named_invoke met de juiste handtekening uitvoeren en alles werkt en werkt.

Stel voor een meer complex voorbeeld dat u elementgewijze vermenigvuldiging van elementen van een std :: array wilt hebben:

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

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

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

live voorbeeld .

Deze elementaire matrixcode kan worden uitgebreid om te werken op tupels of paren of C-stijl arrays, of zelfs containers met variabele lengte als u besluit wat te doen als de lengtes niet overeenkomen.

Je zou ook een element-gewijs type operator kunnen krijgen en lhs *element_wise<'+'>* rhs .

Het schrijven van een *dot* en *cross* productoperators is ook duidelijk gebruik.

Het gebruik van * kan worden uitgebreid om andere scheidingstekens te ondersteunen, zoals + . De delimeter-precidence bepaalt de precidence van de genoemde operator, wat belangrijk kan zijn bij het vertalen van fysische vergelijkingen naar C ++ met minimaal gebruik van extra () s.

Met een kleine wijziging in de bovenstaande bibliotheek kunnen we ->*then* operators ondersteunen en de std::function uitbreiden voordat de standaard wordt bijgewerkt, of monadisch schrijven ->*bind* . Het zou ook een stateful operator kunnen hebben, waarbij we de Op zorgvuldig doorgeven aan de laatste functie voor het aanroepen, waardoor:

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

het genereren van een genoemde container-toevoegende operator in C ++ 17.



Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow