Could operator overloading have worked without references?

前端 未结 5 1901
孤街浪徒
孤街浪徒 2020-12-17 00:07

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

References were introduced primarily to support o

相关标签:
5条回答
  • 2020-12-17 00:24

    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 讨论(0)
  • 2020-12-17 00:28

    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.

    0 讨论(0)
  • 2020-12-17 00:30

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

    0 讨论(0)
  • 2020-12-17 00:30

    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 讨论(0)
  • 2020-12-17 00:39

    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.

    0 讨论(0)
提交回复
热议问题