C++
Surcharge de l'opérateur
Recherche…
Introduction
En C ++, il est possible de définir des opérateurs tels que +
et ->
pour les types définis par l'utilisateur. Par exemple, l'en-tête <string>
définit un opérateur +
pour concaténer des chaînes. Cela se fait en définissant une fonction d’opérateur à l’aide du mot-clé operator
.
Remarques
Les opérateurs pour les types intégrés ne peuvent pas être modifiés, les opérateurs ne peuvent être surchargés que pour les types définis par l'utilisateur. C'est-à-dire qu'au moins l'un des opérandes doit être d'un type défini par l'utilisateur.
Les opérateurs suivants ne peuvent pas être surchargés:
- L'accès membre ou l'opérateur "point"
.
- Le pointeur sur l'opérateur d'accès membre
.*
- L'opérateur de résolution de portée,
::
- L'opérateur conditionnel ternaire,
?:
-
dynamic_cast
,static_cast
,reinterpret_cast
,const_cast
,typeid
,sizeof
,alignof
etnoexcept
- Les directives de prétraitement
#
et##
qui sont exécutées avant toute information de type sont disponibles.
Certains opérateurs ne doivent pas (99,98% du temps) surcharger:
-
&&
et||
(préférez plutôt utiliser la conversion implicite enbool
) -
,
- L'adresse de l'opérateur (unaire
&
)
Pourquoi? Parce qu'ils surchargent les opérateurs auxquels un autre programmeur ne s'attend jamais, entraînant un comportement différent de celui anticipé.
Par exemple, l'utilisateur a défini &&
et ||
ces opérateurs de surcharge perdent leur évaluation de ,
court-circuit et perdent leurs propriétés de séquençage spéciales (17 C ++) , la question de séquençage applique également ,
les surcharges de l' opérateur.
Opérateurs arithmétiques
Vous pouvez surcharger tous les opérateurs arithmétiques de base:
-
+
et+=
-
-
et-=
-
*
et*=
-
/
et/=
-
&
et&=
-
|
et|=
-
^
et^=
-
>>
et>>=
-
<<
et<<=
La surcharge pour tous les opérateurs est la même. Faites défiler pour une explication
Surcharge en dehors de 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;
}
Surcharge à l'intérieur de 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;
}
Note: operator+
devrait retourner par une valeur non-const, car renvoyer une référence n'aurait pas de sens (elle renvoie un nouvel objet) et ne renverrait pas une valeur const
(vous ne devriez généralement pas retourner par const
). Le premier argument est passé par valeur, pourquoi? Car
- Vous ne pouvez pas modifier l'objet d'origine (
Object foobar = foo + bar;
ne devrait pas modifierfoo
après tout, cela n'aurait aucun sens) - Vous ne pouvez pas le faire
const
, parce que vous devez être en mesure de modifier l'objet (car l'operator+
est mis en œuvre en termes d'operator+=
, qui modifie l'objet)
Passer par const&
serait une option, mais vous devrez alors faire une copie temporaire de l'objet passé. En passant par valeur, le compilateur le fait pour vous.
operator+=
renvoie une référence à lui-même, car il est alors possible de les enchaîner (n'utilisez cependant pas la même variable, ce serait un comportement indéfini dû à des points de séquence).
Le premier argument est une référence (nous voulons le modifier), mais pas const
, car alors vous ne pourriez pas le modifier. Le second argument ne doit pas être modifié, et donc, pour des raisons de performances, il est passé par const&
(le passage par la référence const est plus rapide que par valeur).
Opérateurs unaires
Vous pouvez surcharger les 2 opérateurs unaires:
-
++foo
etfoo++
-
--foo
etfoo--
La surcharge est la même pour les deux types ( ++
et --
). Faites défiler pour une explication
Surcharge en dehors de 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;
}
Surcharge à l'intérieur de 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;
}
Remarque: L'opérateur de préfixe renvoie une référence à lui-même, afin que vous puissiez continuer à y effectuer des opérations. Le premier argument est une référence, car l'opérateur de préfixe change l'objet, c'est aussi la raison pour laquelle il n'est pas const
(vous ne pourriez pas le modifier autrement).
L'opérateur postfix renvoie par valeur une valeur temporaire (la valeur précédente) et ne peut donc pas être une référence, car il s'agirait d'une référence à une valeur temporaire, qui serait inutile à la fin de la fonction, car la variable temporaire s'éteint. de portée). Il ne peut pas non plus être const
, car vous devriez pouvoir le modifier directement.
Le premier argument est une référence non const
à l'objet "calling", car s'il était const
, vous ne seriez pas en mesure de le modifier, et si ce n'était pas une référence, vous ne modifieriez pas la valeur d'origine.
C'est à cause de la copie nécessaire dans les surcharges d'opérateur postfix qu'il vaut mieux prendre l'habitude d'utiliser prefix ++ au lieu de postfix ++ dans for
loops. Du point de vue de la boucle for
, ils sont généralement fonctionnellement équivalents, mais l'utilisation du préfixe ++, en particulier avec les "grosses" classes avec beaucoup de membres à copier, peut présenter un léger avantage. Exemple d'utilisation de prefix ++ dans une boucle for:
for (list<string>::const_iterator it = tokens.begin();
it != tokens.end();
++it) { // Don't use it++
...
}
Opérateurs de comparaison
Vous pouvez surcharger tous les opérateurs de comparaison:
-
==
et!=
-
>
et<
-
>=
et<=
La méthode recommandée pour surcharger tous ces opérateurs consiste à implémenter uniquement 2 opérateurs ( ==
et <
), puis à les utiliser pour définir le reste. Faites défiler pour une explication
Surcharge en dehors de 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); }
Surcharge à l'intérieur de 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); }
Les opérateurs renvoient évidemment un bool
, indiquant true
ou false
pour l'opération correspondante.
Tous les opérateurs prennent leurs arguments par const&
, car la seule chose que font les opérateurs est de comparer, donc ils ne doivent pas modifier les objets. Passer par &
(référence) est plus rapide que par valeur, et pour s'assurer que les opérateurs ne le modifient pas, il s'agit d'une référence const
.
Notez que les opérateurs à l'intérieur de la class
/ struct
sont définis comme const
, la raison en est que sans les fonctions const
, la comparaison des objets const
ne serait pas possible, car le compilateur ne sait pas que les opérateurs ne modifient rien.
Opérateurs de conversion
Vous pouvez surcharger les opérateurs de type afin que votre type puisse être implicitement converti dans le type spécifié.
L'opérateur de conversion doit être défini dans une class
/ struct
:
operator T() const { /* return something */ }
Remarque: l'opérateur est const
pour permettre la conversion des objets const
.
Exemple:
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;
Opérateur d'indice de tableau
Vous pouvez même surcharger l'opérateur d'index de tableau []
.
Vous devez toujours (99,98% du temps) implémenter 2 versions, une version const
et une version non const
, car si l'objet est const
, il ne devrait pas pouvoir modifier l'objet renvoyé par []
.
Les arguments sont passés par const&
non par valeur car le passage par référence est plus rapide que par valeur et const
pour que l'opérateur ne modifie pas l'index accidentellement.
Les opérateurs renvoient par référence, car de par leur conception, vous pouvez modifier le retour de l'objet []
, à savoir:
std::vector<int> v{ 1 };
v[0] = 2; //Changes value of 1 to 2
//wouldn't be possible if not returned by reference
Vous ne pouvez surcharger que dans une 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
}
Plusieurs opérateurs d'indice, [][]...
, peuvent être obtenus via des objets proxy. L'exemple suivant d'une classe de matrice simple de rangée majeure montre ceci:
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;
};
Opérateur d'appel de fonction
Vous pouvez surcharger l'opérateur d'appel de fonction ()
:
La surcharge doit être faite à l'intérieur d'une 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, ...);
Par exemple:
struct Sum
{
int operator()(int a, int b)
{
return a + b;
}
};
//Create instance of struct
Sum sum;
int result = sum(1, 1); //result == 2
Opérateur d'assignation
L'opérateur d'affectation est l'un des opérateurs les plus importants car il vous permet de modifier le statut d'une variable.
Si vous ne surchargez pas l'opérateur d'affectation pour votre class
/ struct
, il est automatiquement généré par le compilateur: l'opérateur d'affectation généré automatiquement exécute une "attribution membre", c'est-à-dire en invoquant des opérateurs d'affectation sur chaque membre. à l'autre, un membre à la fois. L'opérateur d'affectation doit être surchargé lorsque l'attribution simple par membres n'est pas adaptée à votre class
/ struct
, par exemple si vous devez effectuer une copie approfondie d'un objet.
La surcharge de l'opérateur d'affectation =
est facile, mais vous devez suivre quelques étapes simples.
- Test d'auto-affectation. Ce contrôle est important pour deux raisons:
- une auto-affectation est une copie inutile, donc cela n'a aucun sens de l'exécuter;
- l'étape suivante ne fonctionnera pas dans le cas d'une auto-affectation.
- Nettoyez les anciennes données. Les anciennes données doivent être remplacées par de nouvelles. Maintenant, vous pouvez comprendre la deuxième raison de l'étape précédente: si le contenu de l'objet a été détruit, une auto-affectation échouera à effectuer la copie.
- Copiez tous les membres. Si vous surchargez l'opérateur d'assignation pour votre
class
ou votrestruct
, il n'est pas généré automatiquement par le compilateur. Vous devrez donc vous charger de copier tous les membres de l'autre objet. - Retourner
*this
. L'opérateur retourne par lui-même par référence, car il permet le chaînage (ieint b = (a = 6) + 4; //b == 10
).
//T is some type
T& operator=(const T& other)
{
//Do something (like copying values)
return *this;
}
Note: other
est passé par const&
, car l'objet assigné ne doit pas être modifié, et le passage par référence est plus rapide que par valeur, et pour s'assurer operator=
ne le modifie pas accidentellement, c'est const
.
L'opérateur d'affectation ne peut être surchargé que dans la class
/ struct
, car la valeur de gauche de =
est toujours la class
/ struct
elle struct
même. Le définir comme une fonction libre n'a pas cette garantie et est interdit à cause de cela.
Lorsque vous le déclarez dans la class
/ struct
, la valeur de gauche est implicitement la class
/ struct
elle struct
même, donc cela ne pose aucun problème.
Opérateur binaire NON
Surcharger le bit bit NOT ( ~
) est assez simple. Faites défiler pour une explication
Surcharge en dehors de class
/ struct
:
T operator~(T lhs)
{
//Do operation
return lhs;
}
Surcharge à l'intérieur de class
/ struct
:
T operator~()
{
T t(*this);
//Do operation
return t;
}
Note: operator~
renvoie par valeur, car il doit renvoyer une nouvelle valeur (la valeur modifiée), et non une référence à la valeur (ce serait une référence à l’objet temporaire, qui aurait une valeur de mémoire dès que l'opérateur est fait). Non const
soit parce que le code d'appel devrait pouvoir le modifier par la suite ( par exemple int a = ~a + 1;
devrait être possible).
A l' intérieur de la class
/ struct
vous devez faire un objet temporaire, parce que vous ne pouvez pas modifier this
, car il modifierait l'objet original, qui ne devrait pas être le cas.
Opérateurs de décalage de bits pour E / S
Les opérateurs <<
et >>
sont couramment utilisés comme opérateurs "write" et "read":
-
std::ostream
surcharges<<
pour écrire des variables dans le flux sous-jacent (exemple:std::cout
) -
std::istream
surcharge>>
pour lire depuis le flux sous-jacent vers une variable (exemple:std::cin
)
La façon dont ils le font est similaire si vous voulez les surcharger "normalement" en dehors de la class
/ struct
, sauf que spécifier les arguments ne sont pas du même type:
- Le type de retour est le flux que vous voulez surcharger (par exemple,
std::ostream
) passé par référence, pour permettre le chaînage (Chaînage:std::cout << a << b;
). Exemple:std::ostream&
-
lhs
serait le même que le type de retour -
rhs
est le type que vous voulez autoriser à surcharger (c'est-à-direT
), passé parconst&
au lieu de valeur pour des raisons de performances (rhs
ne doit pas être modifié de toute façon). Exemple:const Vector&
.
Exemple:
//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;
Nombres complexes revisités
Le code ci-dessous implémente un type de nombre complexe très simple pour lequel le champ sous-jacent est automatiquement promu, suivant les règles de promotion du type du langage, sous l'application des quatre opérateurs de base (+, -, * et /) avec un membre d'un champ différent (que ce soit un autre complex<T>
ou un type scalaire).
Ceci est destiné à être un exemple global couvrant la surcharge de l'opérateur, parallèlement à l'utilisation de base des modèles.
#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;
}
Opérateurs nommés
Vous pouvez étendre C ++ avec des opérateurs nommés qui sont "cités" par des opérateurs C ++ standard.
Nous commençons par une bibliothèque de douzaines de lignes:
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) );
}
}
cela ne fait rien encore.
Premièrement, ajouter des vecteurs
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;
Le noyau ici est que nous définissons un objet append
de type append_t:named_operator::make_operator<append_t>
.
Nous surchargons alors named_invoke (lhs, append_t, rhs) pour les types que nous voulons à droite et à gauche.
La bibliothèque surcharge lhs*append_t
, renvoyant un objet half_apply
temporaire. Il surcharge également half_apply*rhs
pour appeler named_invoke( lhs, append_t, rhs )
.
Nous devons simplement créer le jeton append_t
approprié et effectuer un named_invoke
la signature ADL-friendly named_invoke
de la signature appropriée, et tout se named_invoke
et fonctionne.
Pour un exemple plus complexe, supposons que vous souhaitiez une multiplication d'éléments des éléments d'un tableau 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])...
}};
});
}
}
Ce code de tableau élémentaire peut être étendu pour fonctionner sur des tuples ou des paires ou des tableaux de style C, ou même des conteneurs de longueur variable si vous décidez quoi faire si les longueurs ne correspondent pas.
Vous pouvez également saisir un type d’opérateur élémentaire et obtenir lhs *element_wise<'+'>* rhs
.
L'écriture d'opérateurs de produit *dot*
et *cross*
est également une utilisation évidente.
L'utilisation de *
peut être étendue pour prendre en charge d'autres délimiteurs, comme +
. La précision des délimiteurs détermine la précision de l'opérateur nommé, ce qui peut être important lors de la traduction des équations de physique en C ++ avec une utilisation minimale des extra ()
.
Avec un léger changement dans la bibliothèque ci-dessus, nous pouvons prendre en charge les opérateurs ->*then*
et étendre std::function
avant la mise à jour du standard, ou écrire monadic ->*bind*
. Il pourrait également avoir un opérateur nommé avec état, où nous passerions soigneusement la Op
à la fonction d'invocation finale, permettant ainsi:
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);
};
générer un opérateur nommé contenant le conteneur en C ++ 17.