C++
Sobrecarga del operador
Buscar..
Introducción
En C ++, es posible definir operadores como +
y ->
para tipos definidos por el usuario. Por ejemplo, el encabezado <string>
define un operador +
para concatenar cadenas. Esto se hace definiendo una función de operador utilizando la palabra clave del operator
.
Observaciones
Los operadores para los tipos incorporados no se pueden cambiar, los operadores solo se pueden sobrecargar para los tipos definidos por el usuario. Es decir, al menos uno de los operandos debe ser de un tipo definido por el usuario.
Los siguientes operadores no pueden ser sobrecargados:
- El miembro de acceso o el operador de "punto"
.
- El puntero al operador de acceso miembro
.*
- El operador de resolución de alcance,
::
- El operador condicional ternario,
?:
-
dynamic_cast
,static_cast
,reinterpret_cast
,const_cast
,typeid
,sizeof
,alignof
ynoexcept
- Las directivas de preprocesamiento,
#
y##
, que se ejecutan antes de que esté disponible cualquier tipo de información.
Hay algunos operadores que no debería (99,98% de las veces) de sobrecarga:
-
&&
y||
(Prefiero, en cambio, usar la conversión implícita abool
) -
,
- La dirección del operador (unario
&
)
¿Por qué? Porque sobrecargan a los operadores que otro programador nunca esperaría, lo que resulta en un comportamiento diferente al anticipado.
Por ejemplo, el usuario definió &&
y ||
sobrecargas de estos operadores pierden su evaluación de cortocircuito y pierden sus propiedades de secuenciación especiales (C ++ 17) , la cuestión de la secuencia también se aplica a ,
sobrecargas de operadores.
Operadores aritméticos
Puedes sobrecargar todos los operadores aritméticos básicos:
-
+
y+=
-
-
y-=
-
*
y*=
-
/
y/=
-
&
y&=
-
|
y|=
-
^
y^=
-
>>
y>>=
-
<<
y<<=
La sobrecarga para todos los operadores es la misma. Desplácese hacia abajo para la explicación
Sobrecarga fuera 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;
}
Sobrecarga dentro 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;
}
Nota: el operator+
debe devolver un valor no constante, ya que devolver una referencia no tendría sentido (devuelve un nuevo objeto) ni tampoco devolvería un valor const
(por lo general, no debería devolver por const
). El primer argumento se pasa por valor, ¿por qué? Porque
- No puede modificar el objeto original (
Object foobar = foo + bar;
no debería modificarfoo
después de todo, no tendría sentido) - No puede hacer que sea
const
, porque tendrá que poder modificar el objeto (porque eloperator+
se implementa en términos deoperator+=
, que modifica el objeto)
Pasar por const&
sería una opción, pero luego tendrá que hacer una copia temporal del objeto pasado. Al pasar por valor, el compilador lo hace por ti.
operator+=
devuelve una referencia al mismo, porque es posible encadenarlos (aunque no use la misma variable, sería un comportamiento indefinido debido a los puntos de secuencia).
El primer argumento es una referencia (queremos modificarlo), pero no const
, porque entonces no podrías modificarlo. El segundo argumento no debe modificarse, por lo que, por razones de rendimiento, pasa por const&
(pasar por const es más rápido que por valor).
Operadores unarios
Puede sobrecargar los 2 operadores unarios:
-
++foo
yfoo++
-
--foo
yfoo--
La sobrecarga es la misma para ambos tipos ( ++
y --
). Desplácese hacia abajo para la explicación
Sobrecarga fuera 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;
}
Sobrecarga dentro 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;
}
Nota: El operador de prefijo devuelve una referencia a sí mismo, para que pueda continuar las operaciones en él. El primer argumento es una referencia, ya que el operador de prefijo cambia el objeto, esa es también la razón por la que no es const
(de lo contrario no podría modificarlo).
El operador de postfix devuelve por valor un temporal (el valor anterior), por lo que no puede ser una referencia, ya que sería una referencia a un temporal, que sería un valor de basura al final de la función, porque la variable temporal se apaga. de alcance). Tampoco puede ser const
, porque deberías poder modificarlo directamente.
El primer argumento es una referencia no const
al objeto "llamante", porque si fuera const
, no sería capaz de modificarlo, y si no fuera una referencia, no cambiaría el valor original.
Es debido a la copia necesaria en las sobrecargas de operadores de postfix que es mejor hacer que sea un hábito usar el prefijo ++ en lugar de postfix ++ en bucles for
. Desde la perspectiva del bucle for
, por lo general son funcionalmente equivalentes, pero puede haber una ligera ventaja de rendimiento al usar el prefijo ++, especialmente con clases "grandes" con muchos miembros para copiar. Ejemplo de uso del prefijo ++ en un bucle for:
for (list<string>::const_iterator it = tokens.begin();
it != tokens.end();
++it) { // Don't use it++
...
}
Operadores de comparación
Puede sobrecargar todos los operadores de comparación:
-
==
y!=
-
>
y<
-
>=
y<=
La forma recomendada de sobrecargar a todos esos operadores es implementando solo 2 operadores ( ==
y <
) y luego usándolos para definir el resto. Desplácese hacia abajo para la explicación
Sobrecarga fuera 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); }
Sobrecarga dentro 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); }
Los operadores obviamente devuelven un bool
, indicando true
o false
para la operación correspondiente.
Todos los operadores toman sus argumentos por const&
, porque lo único que hacen los operadores es comparar, por lo que no deben modificar los objetos. Pasar por &
(referencia) es más rápido que por valor, y para asegurarse de que los operadores no lo modifiquen, es una referencia const
.
Tenga en cuenta que los operadores dentro de la class
/ struct
se definen como const
, la razón de esto es que sin las funciones const
, no sería posible comparar objetos const
, ya que el compilador no sabe que los operadores no modifican nada.
Operadores de conversión
Puede sobrecargar los operadores de tipo, de modo que su tipo pueda convertirse implícitamente en el tipo especificado.
El operador de conversión debe estar definido en una class
/ struct
:
operator T() const { /* return something */ }
Nota: el operador es const
para permitir la conversión de objetos const
.
Ejemplo:
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;
Operador de subíndice de matriz
Incluso puede sobrecargar el operador de subíndice de matriz []
.
Siempre debe (99,98% de las veces) implemente 2 versiones, una const
y un no- const
versión, ya que si el objeto es const
, no debería ser capaz de modificar el objeto devuelto por []
.
Los argumentos se pasan por const&
lugar de por valor porque pasar por referencia es más rápido que por valor, y const
para que el operador no cambie el índice accidentalmente.
Los operadores devuelven por referencia, porque por diseño puede modificar la devolución del objeto []
, es decir:
std::vector<int> v{ 1 };
v[0] = 2; //Changes value of 1 to 2
//wouldn't be possible if not returned by reference
Sólo se puede sobrecargar el interior de una class
/ struct
:
//I is the index type, normally an int
T& operator[](const I& index)
{
//Do something
//return something
}
//I is the index type, normally an int
const T& operator[](const I& index) const
{
//Do something
//return something
}
Se pueden lograr múltiples operadores de subíndices, [][]...
, a través de objetos proxy. El siguiente ejemplo de una clase simple de matriz de fila demuestra esto:
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;
};
Operador de llamada de función
Puede sobrecargar la función llamada operador ()
:
La sobrecarga se debe hacer dentro de una class
/ struct
:
//R -> Return type
//Types -> any different type
R operator()(Type name, Type2 name2, ...)
{
//Do something
//return something
}
//Use it like this (R is return type, a and b are variables)
R foo = object(a, b, ...);
Por ejemplo:
struct Sum
{
int operator()(int a, int b)
{
return a + b;
}
};
//Create instance of struct
Sum sum;
int result = sum(1, 1); //result == 2
Operador de asignación
El operador de asignación es uno de los operadores más importantes porque le permite cambiar el estado de una variable.
Si no sobrecarga el operador de asignación para su class
/ struct
, el compilador lo genera automáticamente: el operador de asignación generado automáticamente realiza una "asignación de miembros", es decir, invocando operadores de asignación en todos los miembros, para que se copie un objeto al otro, un miembro a la vez. El operador de asignación debe estar sobrecargado cuando la asignación simple de miembros no es adecuada para su class
/ struct
, por ejemplo, si necesita realizar una copia profunda de un objeto.
Sobrecargar el operador de asignación =
es fácil, pero debe seguir algunos pasos simples.
- Prueba de autoasignación. Esta verificación es importante por dos razones:
- una autoasignación es una copia innecesaria, por lo que no tiene sentido realizarla;
- El siguiente paso no funcionará en el caso de una autoasignación.
- Limpia los datos antiguos. Los datos antiguos deben ser reemplazados por otros nuevos. Ahora, puede comprender el segundo motivo del paso anterior: si se destruyó el contenido del objeto, una autoasignación no podrá realizar la copia.
- Copia todos los miembros. Si sobrecarga el operador de asignación para su
class
o sustruct
, el compilador no lo genera automáticamente, por lo que deberá hacerse cargo de copiar todos los miembros del otro objeto. - Devuelve
*this
. El operador regresa solo por referencia, porque permite el encadenamiento (es decir,int b = (a = 6) + 4; //b == 10
).
//T is some type
T& operator=(const T& other)
{
//Do something (like copying values)
return *this;
}
Nota: other
se pasa por const&
, porque el objeto que se asigna no debe cambiarse, y pasar por referencia es más rápido que por valor, y para asegurarse de que operator=
no lo modifique accidentalmente, es const
.
El operador de asignación solo se puede sobrecargar en la class
/ struct
, porque el valor izquierdo de =
es siempre la class
/ struct
sí. Definirlo como una función gratuita no tiene esta garantía, y por eso no está permitido.
Cuando lo declara en la class
/ struct
, el valor de la izquierda es implícitamente la class
/ struct
sí, por lo que no hay problema con eso.
Operador NO bit a bit
Sobrecargar el bit a bit ( ~
) es bastante simple. Desplácese hacia abajo para la explicación
Sobrecarga fuera de class
/ struct
:
T operator~(T lhs)
{
//Do operation
return lhs;
}
Sobrecarga dentro de class
/ struct
:
T operator~()
{
T t(*this);
//Do operation
return t;
}
Nota: operator~
devuelve por valor, porque tiene que devolver un nuevo valor (el valor modificado), y no una referencia al valor (sería una referencia al objeto temporal, que tendría un valor de basura en él tan pronto como el operador está hecho). const
porque el código de llamada debe poder modificarlo posteriormente (es decir, int a = ~a + 1;
debería ser posible).
Dentro de la class
/ struct
usted tiene que hacer un objeto temporal, porque no se puede modificar this
, ya que modificaría el objeto original, que no debería ser el caso.
Operadores de cambio de bit para E / S
Los operadores <<
y >>
se utilizan comúnmente como operadores de "escritura" y "lectura":
-
std::ostream
overloads<<
para escribir variables en el flujo subyacente (ejemplo:std::cout
) -
std::istream
overloads>>
para leer desde el flujo subyacente a una variable (ejemplo:std::cin
)
La forma en que lo hacen es similar si desea sobrecargarlos "normalmente" fuera de la class
/ struct
, excepto que la especificación de los argumentos no es del mismo tipo:
- Tipo de retorno es el flujo del que desea sobrecargar (por ejemplo,
std::ostream
) pasado por referencia, para permitir el encadenamiento (Encadenamiento:std::cout << a << b;
). Ejemplo:std::ostream&
-
lhs
sería el mismo que el tipo de retorno -
rhs
es el tipo desde el que desea permitir la sobrecarga (es decir,T
), pasado porconst&
lugar de valor por razones de rendimiento (rhs
no debería cambiarse de todos modos). Ejemplo:const Vector&
.
Ejemplo:
//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;
Números complejos revisados
El siguiente código implementa un tipo de número complejo muy simple para el cual el campo subyacente se promueve automáticamente, siguiendo las reglas de promoción del tipo de idioma, bajo la aplicación de los cuatro operadores básicos (+, -, * y /) con un miembro de un campo diferente (ya sea otro complex<T>
o algún tipo escalar).
Se pretende que sea un ejemplo holístico que cubra la sobrecarga de operadores junto con el uso básico de plantillas.
#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;
}
Operadores nombrados
Puede extender C ++ con operadores nombrados que están "cotizados" por los operadores estándar de C ++.
Primero comenzamos con una biblioteca de doce líneas:
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) );
}
}
Esto no hace nada todavía.
Primero, adjuntando vectores.
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;
El núcleo de este caso es que definimos un append
objeto de tipo append_t:named_operator::make_operator<append_t>
.
Luego sobrecargamos named_invoke (lhs, append_t, rhs) para los tipos que queremos a la derecha e izquierda.
La biblioteca sobrecarga lhs*append_t
, devolviendo un objeto half_apply
temporal. También sobrecarga half_apply*rhs
para llamar named_invoke( lhs, append_t, rhs )
.
Simplemente tenemos que crear el token append_t
adecuado y hacer un named_invoke
ADL de la firma adecuada, y todo se conecta y funciona.
Para un ejemplo más complejo, suponga que quiere tener una multiplicación de elementos de un 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])...
}};
});
}
}
Este código de matriz de elementos se puede ampliar para trabajar en tuplas o pares o matrices de estilo C, o incluso contenedores de longitud variable si decide qué hacer si las longitudes no coinciden.
También puede lhs *element_wise<'+'>* rhs
tipo de operador inteligente y obtener lhs *element_wise<'+'>* rhs
.
Escribir operadores de productos *dot*
y *cross*
también son usos obvios.
El uso de *
se puede extender para admitir otros delimitadores, como +
. La precisión del delimitador determina la precisión del operador nombrado, lo que puede ser importante al traducir las ecuaciones físicas a C ++ con un uso mínimo de extra ()
s.
Con un ligero cambio en la biblioteca anterior, podemos admitir ->*then*
operadores y extender la std::function
antes de que se actualice el estándar, o escribir de forma monádica ->*bind*
. También podría tener un operador con nombre de estado, donde pasamos cuidadosamente la Op
hasta la función de invocación final, permitiendo:
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);
};
generando un operador anexado de contenedores nombrado en C ++ 17.