11

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 *);
};
fredoverflow
  • 256,549
  • 94
  • 388
  • 662
  • 1
    why did you choose substraction as an example?? it doesn't need references to work... – Karoly Horvath Nov 04 '11 at 10:33
  • @yi_H: I didn't choose the example of subtraction, Bjarne did. How would *you* solve it without references and still get call-by-reference semantics? – fredoverflow Nov 04 '11 at 10:36
  • 1
    @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 Nov 04 '11 at 10:37
  • @FredOverflow: "References were introduced into C++ to support operator overloading" .. "Bjarne's solution to this problem was to introduce reference types into the language". that's what *you* said, not Bjarne. – Karoly Horvath Nov 04 '11 at 10:40
  • 1
    @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](http://www2.research.att.com/~bs/bs_faq2.html#pointers-and-references), albeit with addition instead of subtraction: `the direct reason I introduced them in C++ was to support operator overloading` – fredoverflow Nov 04 '11 at 10:43
  • 1
    @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 Nov 04 '11 at 10:47
  • @FredOverflow: well, that's a stupid example and I don't care that Bjarne wrote it. – Karoly Horvath Nov 04 '11 at 10:49
  • @yi_H: Would you mind explaining why the example is stupid? And what would be a better example? Thanks. – fredoverflow Nov 04 '11 at 10:56
  • @FredOverflow: as I said, it works fine without references (not as efficient though if the compiler can't optimise it). for the other Q read sbi's comment again. – Karoly Horvath Nov 04 '11 at 10:58
  • So what would happen if I define my own conversion operator from some type `Foo` to `Foo*`? Which would get used when I pass a `Foo` object to an operator? – jalf Nov 04 '11 at 12:11
  • People get confused over all sorts of things all the time, whether it's statitics, evolution, or women. That doesn't mean that any of those things are themselves inherently wrong. Saying that something confuses you, or "confuses people", is hardly an argument for or against anything. – Kerrek SB Nov 04 '11 at 12:16
  • @Kerrek: ...unless reducing confusion at no loss increases productivity. We're in the programming domain: clarity is something to strive for. – GManNickG Nov 04 '11 at 12:33
  • Looking through the answers and your comments, it sounds to me like you're basically reintroducing references in a more complex form, with more inconsistent syntax, and more pitfalls. Handling all the cases pointed out in the answers using pointers and special context-sensitive conversion rules and giving operators a "special" status with semantics different from that of regular functions much more complex than just adding references. – jalf Nov 04 '11 at 12:50
  • @GMan: That's true, but in the present case it's a very subjective matter what is confusing and what is not. Programming is a complex subject, so any number of things are potentially confusing. The question is which features allow a *competent* programmer to work efficiently. In the case of references, one might find it even *more* confusing if pointers and references were all syntactically indistinguishable... The question whether references are inevitable is good, I just don't like the implication that including them was a bad design choice. – Kerrek SB Nov 04 '11 at 12:51
  • 2
    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 Nov 04 '11 at 15:30

5 Answers5

8

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.

Community
  • 1
  • 1
sbi
  • 219,715
  • 46
  • 258
  • 445
  • Not wide-sweeping, only if you try to call a function that accepts pointers, but the arguments are objects. Sort of like array-to-pointer decay. – fredoverflow Nov 04 '11 at 10:30
  • @Fred: Yeah, you are right, I should edit that sentence to say "_very_ wide-sweeping" instead. `:)` Much more appropriate. – sbi Nov 04 '11 at 10:33
  • 1
    But you have the exact same ambiguity problem with references and overloading: you cannot have two functions `void f(Foo);` and `void f(Foo&);` at the same time. The compiler would not be able to decide which one to call if you passed an lvalue to `foo`. – fredoverflow Nov 04 '11 at 10:38
  • @Fred: That is true. Well, it was just a minor point added as an afterthought anyway... – sbi Nov 04 '11 at 10:40
  • 2
    @Fred: but that ambiguity only exists between two user-defined functions. But `operator-` for pointers already exists, so the ambiguity there would be unavoidable. – jalf Nov 04 '11 at 11:39
  • @jalf: How would it be unavoidable? `&b - &a` would mean pointer subtraction as usual, whereas `b - a` would call the user-defined `operator-` with (implicitly generated) pointers to `b` and `a`. – fredoverflow Nov 04 '11 at 11:45
  • 3
    @FredOverflow: Wow, so you're saying "if I call the function with a type other than pointers, then they are converted to pointers, and the user-defined function which takes pointers will be called. But if I call with pointers, the built-in function which takes pointers will be called". That... is... **evil** – jalf Nov 04 '11 at 11:52
  • 1
    @jalf: Yes, but I think you make it sound more dangerous and scary than it actually would be :) Again, this would only apply to operators, not ordinary functions. – fredoverflow Nov 04 '11 at 11:58
  • @FredOverflow: convince me. I think it sounds far more dangerous and scary than I can express. – jalf Nov 04 '11 at 11:58
  • @jalf: There were no problems with the copy constructor and assignment operator taking `const Foo*` arguments prior to the introduction of references, at least none that I have ever heard of. Other than that, I have no "real world evidence", of course. – fredoverflow Nov 04 '11 at 12:04
  • @FredOverflow: but (a) those have no alternative meaning they can conflict with. Writing `Foo foo(somePointer)` isn't valid unless a constructor taking a pointer exists. But `a - b` is valid for pointers already, and you'd be introducing *another* meaning for it. And (b), did the copy ctor and assignment operator prior to references allow you to pass objects and have them implicitly converted to pointers as you propose? – jalf Nov 04 '11 at 12:08
  • @jalf: I strongly believe the answer to b) is yes, but I would have to consult TD&E for this, and I don't have it here right now. Let me get back to you tonight. – fredoverflow Nov 04 '11 at 12:12
  • 1
    @jalf: TD&E is not explicit about the issue, but from the added quote in my question, it *seems* the answer to b) is yes. The only way to be really sure would be sending an eMail to Bjarne. – fredoverflow Nov 04 '11 at 18:24
6

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)?

jalf
  • 243,077
  • 51
  • 345
  • 550
  • There is no implicit conversion in `bar(foo)`, because `bar` is not an operator. – fredoverflow Nov 04 '11 at 12:06
  • @FredOverflow: it was a generalized example. `bar` could be an operator. – jalf Nov 04 '11 at 12:10
  • 1
    How would the non-nullness of references allow for better optimizations? Since dereferencing the null pointer is undefined behavior, I honestly don't see how working with non-null pointers could be any slower than working with references. – fredoverflow Nov 04 '11 at 12:14
  • The "different function" argument is a corner case, right? Or are there any other examples besides pointer subtraction? – fredoverflow Nov 04 '11 at 12:15
  • I don't think your "fail silently" argument applies. If there is no user-defined `operator-`, then `a - b` is a compile-time error, because the conversion only happens as an intermediate step in argument passing. – fredoverflow Nov 04 '11 at 12:28
  • 1
    @FredOverflow: that seems like a circular definition: the conversion only happens if there is a candidate function to call. But in order to determine if there is a candidate function to call, you need to perform the conversion. Of course, you can work your way out of that one, but you're not making the compiler's job any easier. You're effectively defining a new *type* of implicit conversions, one which has to fit in with the existing mess of conversion rules – jalf Nov 04 '11 at 12:40
2

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.

Ad N
  • 7,930
  • 6
  • 36
  • 80
  • Note that my "proposal" only concerns operators, not ordinary methods, because operators is where Bjarne justified the need to introduce references. – fredoverflow Nov 04 '11 at 11:31
  • 2
    Operators were where Bjarne *first* encountered the need for references. Simply solving the operator case doesn't prove that "C++ would've been better off without references". – jalf Nov 04 '11 at 12:06
  • @jalf: You don't need references for ordinary functions, because you can just pass pointers. Some coding conventions even mandate that you pass by pointer instead of by reference. It makes the intent obvious at call site. – fredoverflow Nov 04 '11 at 12:20
  • 1
    @FredOverflow: yes, but my point is that other uses of references may have appeared since then. So in order to show that C++ could work without references, you'll need to show that there are no other parts of the language where references are a significant advantage – jalf Nov 04 '11 at 12:37
  • @FredOverflow: For my mind consistency, I tend to consider that operators really behave like any other method, with the only added option that they offer a "shortcut call syntax" (up to you to use it, or to write full size call). I would be more afraid if that implicit object-to-pointer cast was happening only with operators. But to stick to operators, continuing with the const reference advantage. You could not write concatenation String::operator+ to work with : `std::string mess="Human"; mess+" after all";` with pointers to string only. You either need const reference or object copy here – Ad N Nov 04 '11 at 13:12
0

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.

Flexo
  • 87,323
  • 22
  • 191
  • 272
  • But I never proposed to "copy everything". I proposed to use good old pointers for call by reference, without having to use the `&` operator at call site (which was Bjarne's main concern). – fredoverflow Nov 04 '11 at 11:06
  • @FredOverflow - updated to show why I don't think the implict `&` idea would be semantically better than references are. – Flexo Nov 04 '11 at 11:18
  • Do you have a realistic example where you would want to pass a void pointer to an overloaded operator? – fredoverflow Nov 04 '11 at 11:34
  • 2
    `operator <<` when writing to a stream, for example? – jalf Nov 04 '11 at 12:05
  • @jalf: Oh, that's a good example! I guess the proposal would simply forbid multiple conversion stages. If an operator takes a `Foo*`, you *must* provide a `Foo`. In the case of a `void*`, there would simply be no `void` that you could pass, because there are no `void` objects. – fredoverflow Nov 04 '11 at 12:23
  • 1
    It doesn't strike you as odd that "if an operator takes an [X], you must pass it *something different*"? – jalf Nov 04 '11 at 12:45
  • @jalf: We have accepted that postfix operators take an additional `int` parameter, yet none is actually ever passed to them :) – fredoverflow Nov 04 '11 at 17:54
  • @FredOverflow - I think that was probably a mistake, but at least it's only for a very localised thing rather than something you need to be aware of potentially anywhere. (I don't see why `operator++postfix` wouldn't have been reasonable even if using `++operator` vs `operator++` would have made the grammar really ugly) – Flexo Nov 04 '11 at 18:01
0

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.

Dennis Zickefoose
  • 10,791
  • 3
  • 29
  • 38