views:

412

answers:

3

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?

+1  A: 

Standard (Working Draft 2010-02-16, § 5.5) says:

The result of an ->* expression is an lvalue only if its second operand is a pointer to data member. If the second operand is the null pointer to member value (4.11), the behavior is undefined.

You may want this behavior to be well-defined. For example, check if it is a null pointer and handle this situation. SO I quess it is right decision for a standard to allow ->* overloading.

topright
Clause 5 doesn't apply to overloads. For example, an `operator/` overload can define behavior for its second operand being zero, and you can legally use that.
Potatoswatter
+1  A: 

I agree with you that there is an incoherence on the standard, It doesn't allows overloading of operator[] with non-member functions and allows it for operator->. For my point of view operator[] is to arrays as operator-> is to structs/classes (a getter). Members of an array are selected using an index. Members of a struct are selected using member pointers.

The worst is that we can be tempted to use ->* instead of operator[] to get an array like element

int& operator->*(Array& lhs, int i);

Array a;

a ->* 2 = 10;

There is also another possible incoherence. We can use a non member function to overload operator+= and all the operator of the form @=) and we cannot do it for operator=.

I don't really know what is the rationale to make the the following 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;

and forbidding

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

z = x;

Sorry to not answer to your question, but adding even more confusion.

Vicente Botet Escriba
`operator ->` and `operator @=` must be member functions. Free functions may be supported by your compiler, but that is certainly nonstandard.
Potatoswatter
operator @= can be overloaded by non-member functions. See the standard.
Vicente Botet Escriba
§13.5.3 doesn't differentiate between the assignment operators. It says "an assignment operator shall be implemented by a non-static member function with exactly one parameter." §5.17 says "There are several assignment operators, all of which group right-to-left." Unless there's something I'm missing, `operator=` isn't supposed to be special.
Potatoswatter
AndreyT
@Potatoswatter: I remember it was covered by a defect report. Here it is actually: http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#221. The 13.5.3 is intended to refer to simple assignment only. Compound assignment operators are allowed to be overloaded by non-member functions, while simple assignment must be a member. So simple assignment `operator =` is special.
AndreyT
@Andrey: Huh, interesting. The rationale revolves how the ARM worked around `bool` not being built-in?? I guess compatibility is important… Why does the Additional Note from October 2000 appear after the proposed resolution from April 2001? And the resolution was adopted into the FCD, but 13.5.3 wasn't changed so it's pretty much as unclear as ever…
Potatoswatter
Potatoswatter
@Potatowatter "Basic assignment" = and "assignment by operator" += follow different rules. See here <http://en.wikipedia.org/wiki/Operators_in_C_and_C%2B%2B>. Sorry I have not found it on the standard.
Vicente Botet Escriba
@AndreyT operator= can also be used to define other functions than the default copy constructor. This explain the overload operator= with the same signature than the default copy constructors but not the other overloading.
Vicente Botet Escriba
@Potatowatter Thanks for the pointer. Does it means that C++0x will not be backward compatible with C++98, or at the end I was right about the ability of overloading by non-member functions?
Vicente Botet Escriba
@Vicente: I don't exactly understand what you are trying to say about copy constructors and what it has to do with `operator=`
AndreyT
@AndreyT Sorry, I wanted to say 'default assignment'.X
Vicente Botet Escriba
+1  A: 

Googling around a bit, I found more instances of people asking whether operator->* is ever used than actual suggestions.

A couple places suggest T &A::operator->*( T B::* ). Not sure whether this reflects designer's intent or a misimpression that T &A::operator->*( T A::* ) is a builtin. Not really related to my question, but gives an idea of the depth I found in online discussion & literature.

There was a mention of "D&E 11.5.4" which I suppose is Design and Evolution of C++. Perhaps that contains a hint. Otherwise, I'm just gonna conclude it's a bit of useless ugliness that was overlooked by standardization, and most everyone else too.

Edit See below for a paste of the D&E quote.

To put this quantitatively, ->* is the tightest binding operator that can be overloaded by a free function. All the postfix-expression and unary operators overloads require nonstatic member function signatures. Next precedence after unary operators are C-style casts, which could be said to correspond to conversion functions (operator type()), which also cannot be free functions. Then comes ->*, then multiplication. ->* could have been like [] or like %, they could have gone either way, and they chose the path of EEEEEEVIL.

Potatoswatter
Does this answer your question? I didn't know that we can accept our own answer :)
Vicente Botet Escriba
@Vicente: I considered accepting yours, but it still erroneously says that non-member `operator->` is allowed and the rationale (or 2) for allowing `operator+=` but disallowing `operator=` was worked out in the comments. And I wasn't really looking for (dis)agreement, the question was *Did the standards committee forget to prevent this, was it considered too ugly to bother, or are there real-world use cases?*, which is a matter of finding some references or evidence to support one of the conclusions.
Potatoswatter
I'm not really happy with this self-answer, but I was spurred on by a downvote (it may be rude to leave an open-ended question unanswered so long) and there seems to be a lack of evidence, which would support the conclusion that it was overlooked.
Potatoswatter
Here's the quote from D `->*` behaves just like any other binary operator. `operator .*` wasn't included among the operators a programmer could overload for the same reason `operator .` wasn't."
Doug
Doug