Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Are free operator->* overloads evil?

I was perusing section 13.5 after refuting the notion that built-in operators do not participate in overload resolution, and noticed that there is no section on operator->*. It is just a generic binary operator.

Its brethren, operator->, operator*, and operator[], are all required to be non-static member functions. This precludes definition of a free function overload to an operator commonly used to obtain a reference from an object. But the uncommon operator->* is left out.

In particular, operator[] has many similarities. It is binary (they missed a golden opportunity to make it n-ary), and it accepts some kind of container on the left and some kind of locator on the right. Its special-rules section, 13.5.5, doesn't seem to have any actual effect except to outlaw free functions. (And that restriction even precludes support for commutativity!)

So, for example, this is perfectly 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; } 

It's easy to find uses, but alternative syntax tends not to be that bad. For example, scaled indexes for 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 

Did the standards committee forget to prevent this, was it considered too ugly to bother, or are there real-world use cases?

like image 589
Potatoswatter Avatar asked Apr 23 '10 07:04

Potatoswatter


People also ask

What is true about operator overloading?

Which is the correct statement about operator overloading? Explanation: Both arithmetic and non-arithmetic operators can be overloaded. The precedence and associativity of operators remains the same after and before operator overloading. 10.

Can the -> operator be overloaded?

The class member access operator (->) can be overloaded but it is bit trickier. It is defined to give a class type a "pointer-like" behavior. The operator -> must be a member function.

What operators we can't overload?

You cannot overload the following operators: . You cannot overload the preprocessor symbols # and ## . An operator function can be either a nonstatic member function, or a nonmember function with at least one parameter that has class, reference to class, enumeration, or reference to enumeration type.

Why is operator overloading used in C++?

It allows you to provide an intuitive interface to users of your class, plus makes it possible for templates to work equally well with classes and built-in/intrinsic types. Operator overloading allows C/C++ operators to have user-defined meanings on user-defined types (classes).


1 Answers

The best example I am aware of is Boost.Phoenix, which overloads this operator to implement lazy member access.

For those unfamiliar with Phoenix, it is a supremely nifty library for building actors (or function objects) that look like normal expressions:

( 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 

It achieves the above by overloading operator% and operator==. - applied to the actor arg1 these operators return another actor. The range of expressions which can be built in this manner is extreme:

// 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"     ]   ] ); 

After you have been using Phoenix for a short while (not that you ever go back) you will try something like this:

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; 

Which will fail, because of course Phoenix's actors do not have a member ValidStateBit. Phoenix gets around this by overloading 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->*'s arguments are:

  • LHS: an actor returning MyObj *
  • RHS: address of a member

It returns an actor which evaluates the LHS and looks for the specified member in it. (NB: You really, really want to make sure that arg1 returns MyObj * - you have not seen a massive template error until you get something wrong in Phoenix. This little program generated 76,738 characters of pain (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; } 
like image 135
Kietz Avatar answered Sep 23 '22 11:09

Kietz