7
votes

According to Bjarne Stroustrup, references were introduced into C++ to support operator overloading:

References were introduced primarily to support operator overloading.

C passes every function argument by value, and where passing an object by value would be inefficient or inappropriate the user can pass a pointer. This strategy doesn't work where operator overloading is used. In that case, notational convenience is essential because users cannot be expected to insert address-of operators if the objects are large. For example:

a = b - c;

is acceptable (that is, conventional) notation, but

a = &b - &c;

is not. Anyway, &b - &c already has a meaning in C, and I didn't want to change that.

Having both pointers and references in the language is a constant source of confusion for C++ novices.

Couldn't Bjarne have solved this problem by introducing a special language rule that allowed object arguments to decay into pointers if a user-defined operator function exists that takes such pointers?

The declaration and usage of subtraction would then have looked like:

Foo operator-(const Foo* x, const Foo* y);
a = b - c;

Was such a solution ever proposed/considered? Would there be any serious downsides to it?

Yes I know, references provide other advantages due to their restrictions, but that's not the point.

Interestingly, the assignment operator of C with classes seems to have worked exactly like that:

Changing the meaning of assignment for objects of a class [...] is done by declaring a class member function called operator=. For example:

class x {
public:
    int a;
    class y * p;
    void operator = (class x *);
};
5
why did you choose substraction as an example?? it doesn't need references to work...Karoly Horvath
@yi_H: Actually, I cannot think of any operator that absolutely "needs" references. Even unary prefix operator*() could, syntactically, return a copy. Edit: Oh, wait. I can't think of operator+=() and the likes to work without (some form of) references.sbi
@yi_H: If you read The Design and Evolution of C++, you will find Bjarne's argument. He makes a similar argument in one of his FAQs, albeit with addition instead of subtraction: the direct reason I introduced them in C++ was to support operator overloadingfredoverflow
@sbi: Before the introduction of references types into the language, the assignment operator did actually look like operator=(const Foo* rhs); which makes you wonder why Bjarne didn't stay on that road...fredoverflow
Realistically, this would just be substituting one set of amateur programmer questions with another set. Instead of "why can't I reseat references" you get "doesn't foo + foo copy its arguments". Instead of "why can't I have an array of references" you get "why does operator +(foo*, foo*) fail when provided two pointers".Dennis Zickefoose

5 Answers

7
votes

IME, automatic conversion is the bane of C++. Let's all write thankful emails to Bjarne Stroustrup for not adding a wide-sweeping automatic conversion from object to pointer.

If you look for a technical reason: In C++, the subtraction of pointers, even of pointers to user-defined types, is already well-defined. Also, this would lead to ambiguities with overloaded versions of operators that take objects per copy.

5
votes

I don't see how this solves the problem: operator- called on pointers already has a meaning.

You'd be defining an operator- for Foo* arguments, but that already exists.

Then you'd need some contrived semantics that "when called explicitly with pointers, the pointer arithmetic operator is called. When called with object lvalues, they decay into pointers, and then the overloaded version is called". Which, to be honest, seems far more contrived, and much less intuitive than just adding references.

And then inside the operator-, I get the arguments as pointers, and then I'd better make sure to dereference those if I need to call another (or the same) operator from there. Otherwise I'd accidentally end up performing pointer arithmetics.

You're proposing an implicit conversion which has different semantics than doing the conversion yourself. That is:

Foo foo;
bar(foo); 

performs an implicit conversion from Foo to Foo*, and then a function with the signature void bar(Foo*) is called.

But this code would do something completely different:

Foo foo;
bar(&foo);

that would also convert to a Foo*, and it would also call a function with the signature void bar(Foo*), but it would potentially be a different function. (In the case of operator-, for example, one would be the user-overloaded operator, and the other would be the standard pointer arithmetic one.

And then consider template code. In general, implicit conversions make templates really painful because the type passed in might not be the type you want to operate on.

Of course, there are more practical problems too:

References enable optimizations that aren't possible with pointers. (The compiler can assume they're never null, and it may be better able to do aliasing analysis)

Moreover, code would fail silently if no matching operator- can be found. Instead of telling me that, the compiler would implicitly start doing pointer arithmetics. Not a deal-breaker, but I really really prefer errors to be caught early where possible.

And one of the goals with C++ was to make it generic: to avoid having "magic" types or functions. In real-world C++, operators are very much just functions with a different syntax. With this rule, they would have different semantics as well. (What if the operator is called with function syntax? operator+(a, b)?

2
votes

C++ aims at being strongly typed, so trying to be consistent with that philosophy, it makes sense that an object is-not-a pointer to that object, and I totally agree with sbi about how great it is not to have automatic conversion happening all around (I have in mind multi-contributors projects).

To address your concern more specifically, people learning C++ can get confused at first by references vs. pointer, but I am not sure it would clarify anything for them to have this kind of automatic conversion happening on their behalf.
For example :

Foo ** operator+(Foo **lhs, Foo **rhs)
{...}

Foo *varFoo1,*varFoo2;
varFoo1 + &varFoo2;

Following the hypothetical implicit object-to-pointer, should varFoo1 be accepted as an argument to a method expecting Foo ** ? Because the method expects a pointer to Foo * and varFoo1 *is-a* Foo *.

An other advantage of reference used as arguments :
const references can receive a rvalue as argument (ex. the classic string literal), while pointers can't.

0
votes

If you accept that doing it with pointers (either implicitly or explicitly) would introduce really confusing semantics (am I operating on the pointer or the pointee?) then without references that only leaves call by value. (Besides your Foo c = &b - &a; example consider the case where you wanted to write an operator that really did use a pointer as one of it's arguments)

I don't think pointers without & at the call site is usefully-workable and certainly no better than references. If you make it a special feature of operators and only operators then that moves the behaviour well into the "cryptic" special case realm. If you want to reduce the "special case" aspect of it by making it a general feature then I don't think it's helpful or useful over the call by reference as it stands.

For example if I want to write an operator that takes a Foo and a void* I can write:

Foo operator+(const Foo& f, void *ptr);

Under your proposed rules that would become: Foo operator+(Foo *f, void *ptr);. The problem as I see it would then be that even though I wanted ptr to explicitly be a pointer by the "implict & rule" it would accept anything and there doesn't seem to be a way for me to disallow the automatic conversion. So double d; Foo f; f = f + d; would match that, as would int i; Foo f; f = f + i;, potentially in two different ways!

Call by value might have worked and made sense for "simple" types, and you could use smart pointers for the cases where you really don't want to have to take a copy of each operand. In general though the idea of being forced to copy everything seems very unclean compared to a reference based approach.

0
votes

I've never read the D&E book, but my understanding is that references weren't added to make it look better when passing arguments to a function, but to make it look better when yielding values from them. Both simple and compound assignment and subscript operators all result in lvalues when used on built-in types, but there is no way to do the same with built in types without references.

I also wonder how you would go about implementing an operator that operates on both a type and a pointer to a type.

struct Foo { };
struct Bar { };
Foo operator +(Foo&, Bar*);
Bar operator +(Foo*, Bar&);
Foo operator +(Bar*, Foo&);
Bar operator +(Bar&, Foo*);

versus

struct Foo { };
struct Bar { };
Foo operator +(Foo*, Bar*);
Bar operator +(Foo*, Bar*);
Foo operator +(Bar*, Foo*);
Bar operator +(Bar*, Foo*);

The same thing could be shown with just one type, but simply saying "don't do that" seems reasonable in that case... when multiple types are involved, the chances of accidentally introducing an ambiguity increases.