overload operator c++ operator-overloading

overload - operator[] c++



¿El operador libre->*sobrecarga el mal? (4)

Estaba repasando la sección 13.5 después de refutar la noción de que los operadores incorporados no participan en la resolución de sobrecarga, y noté que no hay una sección en el operator->* . Es solo un operador binario genérico.

Se requiere que sus hermanos, operator-> , operator* y operator[] sean funciones miembro no estáticas. Esto excluye la definición de una sobrecarga de función libre para un operador comúnmente utilizado para obtener una referencia de un objeto. Pero el operator->* poco común operator->* queda fuera.

En particular, el operator[] tiene muchas similitudes. Es binario (perdieron una oportunidad de oro para hacerlo nula), y acepta algún tipo de contenedor a la izquierda y algún tipo de localizador a la derecha. Su sección de reglas especiales, 13.5.5, no parece tener ningún efecto real excepto prohibir las funciones gratuitas. (¡Y esa restricción incluso excluye el soporte para la conmutatividad!)

Entonces, por ejemplo, esto es perfectamente legal :

#include <utility> #include <iostream> using namespace std; template< class T > T & operator->*( pair<T,T> &l, bool r ) { return r? l.second : l.first; } template< class T > T & operator->*( bool l, pair<T,T> &r ) { return r->*l; } int main() { pair<int, int> y( 5, 6 ); y->*(0) = 7; y->*0->*y = 8; // evaluates to 7->*y = y.second cerr << y.first << " " << y.second << endl; }

Es fácil encontrar usos, pero la sintaxis alternativa no es tan mala. Por ejemplo, índices escalados para vector :

v->*matrix_width[2][5] = x; // ->* not hopelessly out of place my_indexer<2> m( v, dim ); // my_indexer being the type of (v->*width) m[2][5] = x; // it is probably more practical to slice just once

¿El comité de normas se olvidó de prevenir esto? ¿Se consideró demasiado feo molestar o hay casos de uso en el mundo real?


Buscando en Google un poco, encontré más ejemplos de personas que preguntaban si el operator->* alguna vez se usaba en lugar de las sugerencias reales.

Un par de lugares sugieren T &A::operator->*( TB::* ) . No estoy seguro de si esto refleja la intención del diseñador o una mala impresión de que T &A::operator->*( TA::* ) es un built-in. No está realmente relacionado con mi pregunta, pero da una idea de la profundidad que encontré en la discusión y la literatura en línea.

Hubo una mención de "D & E 11.5.4", que supongo es Diseño y Evolución de C ++. Tal vez eso contenga una pista. De lo contrario, voy a concluir que es un poco de fealdad inútil que se pasó por alto por la estandarización, y la mayoría de los demás también.

Editar Vea a continuación una copia de la cita de D & E.

Para poner esto cuantitativamente, ->* es el operador de enlace más ajustado que puede ser sobrecargado por una función libre. Todas las sobrecargas de postfix-expression y de operadores unarios requieren firmas de funciones de miembros no estáticos. La siguiente prioridad después de los operadores unarios son las versiones en C, que se podría decir que corresponden a las funciones de conversión ( operator type() ), que tampoco pueden ser funciones libres. Luego viene ->* , luego la multiplicación. ->* podría haber sido como [] o como % , podrían haber ido en cualquier dirección, y eligieron el camino de EEEEEEVIL .


El mejor ejemplo del que soy consciente es Boost.Phoenix , que sobrecarga a este operador para implementar el acceso de miembros perezosos.

Para aquellos que no están familiarizados con Phoenix, es una biblioteca extremadamente ingeniosa para construir actores (u objetos de función) que se parecen a las expresiones normales:

( arg1 % 2 == 1 ) // this expression evaluates to an actor (3); // returns true since 3 % 2 == 1 // these actors can also be passed to standard algorithms: std::find_if(c.begin(), c.end(), arg1 % 2 == 1); // returns iterator to the first odd element of c

Lo consigue al sobrecargar al operator% y al operator== . - aplicado al actor arg1 estos operadores devuelven otro actor. El rango de expresiones que se pueden construir de esta manera es extremo:

// print each element in c, noting its value relative to 5: std::for_each(c.begin(), c.end(), if_(arg1 > 5) [ cout << arg1 << " > 5/n" ] .else_ [ if_(arg1 == 5) [ cout << arg1 << " == 5/n" ] .else_ [ cout << arg1 << " < 5/n" ] ] );

Después de que hayas usado Phoenix por un tiempo (aunque nunca vuelvas) intentarás algo como esto:

typedef std::vector<MyObj> container; container c; //... container::iterator inv = std::find_if(c.begin(), c.end(), arg1.ValidStateBit); std::cout << "A MyObj was invalid: " << inv->Id() << std::endl;

Lo cual fallará, porque, por supuesto, los actores de Phoenix no tienen un miembro ValidStateBit . Phoenix soluciona esto al sobrecargar al operator->* :

(arg1 ->* &MyObj::ValidStateBit) // evaluates to an actor (validMyObj); // returns true // used in your algorithm: container::iterator inv = std::find_if(c.begin(), c.end(), (arg1 ->* &MyObj::ValidStateBit) );

operator->* argumentos del operator->* son:

  • LHS: un actor que regresa MyObj *
  • RHS: dirección de un miembro

Devuelve un actor que evalúa el LHS y busca el miembro especificado en él. (NB: Realmente, realmente quieres asegurarte de que arg1 devuelva MyObj * - no has visto un error de plantilla masivo hasta que obtienes algo incorrecto en Phoenix. Este pequeño programa generó 76,738 caracteres de dolor (Boost 1.54, gcc 4.6):

#include <boost/phoenix.hpp> using boost::phoenix::placeholders::arg1; struct C { int m; }; struct D { int n; }; int main() { ( arg1 ->* &D::n ) (new C); return 0; }


Estándar (Working Draft 2010-02-16, § 5.5) dice:

El resultado de una expresión -> * es un valor l solo si su segundo operando es un puntero al miembro de datos. Si el segundo operando es el puntero nulo al valor del miembro (4.11), el comportamiento no está definido.

Es posible que desee que este comportamiento esté bien definido . Por ejemplo, compruebe si es un puntero nulo y maneje esta situación. Entonces, me pregunto si es correcto que una norma permita una sobrecarga de>> *.


Estoy de acuerdo con usted en que hay una incoherencia en el estándar, no permite la sobrecarga del operator[] con funciones que no son miembros y lo permite para el operator->* . Para mi punto de vista operator[] es para matrices como operator->* es para structs / classes (un getter). Los miembros de una matriz se seleccionan usando un índice. Los miembros de una estructura se seleccionan usando punteros de miembro.

Lo peor es que podemos tener la tentación de usar ->* lugar de operator[] para obtener un elemento similar a una matriz

int& operator->*(Array& lhs, int i); Array a; a ->* 2 = 10;

También hay otra posible incoherencia. Podemos usar una función no miembro para sobrecargar el operator+= y todos los operadores de la forma @= ) y no podemos hacerlo para operator= .

Realmente no sé cuál es la razón para hacer que el siguiente legal

struct X { int val; explicit X(int i) : val(i) {} }; struct Z { int val; explicit Z(int i) : val(i) {} }; Z& operator+=(Z& lhs, const X& rhs) { lhs.val+=rhs.val; return lhs; } Z z(2); X x(3); z += x;

y prohibido

Z& operator=(Z& lhs, const X& rhs) { lhs.val=i; return lhs; } z = x;

Lamento no responder a su pregunta, pero agregando aún más confusión.