Destructors, const structs, and opEquals

Andrei Alexandrescu SeeWebsiteForEmail at erdani.org
Sat Dec 4 12:58:43 PST 2010


On 12/4/10 2:39 PM, Don wrote:
> Andrei Alexandrescu wrote:
>> On 12/4/10 9:23 AM, Don wrote:
>>> Andrei Alexandrescu wrote:
>>>> On 12/4/10 12:42 AM, Don wrote:
>>>>> Officially, opEquals has to have the signature:
>>>>>
>>>>> struct Foo {
>>>>> bool opEquals(const ref Foo x) const {...}
>>>>> }
>>>>
>>>> This is a compiler bug. For structs there should be no official
>>>> implementation of opEquals, opCmp etc. All the compiler needs to worry
>>>> about is to syntactically translate a == b to a.opEquals(b) and then
>>>> let the usual language rules resolve the call.
>>>
>>> Fine, but try to implement a generic type which supports ==.
>>> For example, Tuple in std.typecons.
>>> The only reason that many of the Tuple unit tests pass is that opEquals
>>> never gets instantiated.
>>
>> I think it should be as follows:
>>
>> bool opEquals(auto ref inout Tuple rhs) inout {
>> foreach (i, T; Types) {
>> if (this[i] != rhs[i]) return false;
>> }
>> return true;
>> }
>>
>> It looks a bit alembicated but let's not forget that Tuple is supposed
>> to be very flexible and to do a lot of things.
>
> Ouch.
>
> The semantics of == are very well defined, and simple. Always, you want
> read-only access to the two objects, in the fastest possible way.
> I don't see why the complexity of the object should have any influence
> on the signature of ==. If there's a method which works correctly and
> efficiently in every case, why isn't it the only way?

It's not the complexity of the object as much as "don't pay for const if 
you don't use it". If Tuple's opEquals is implemented as above, it works 
with code that doesn't use const at all. Tack a const on to it, 
everybody must define opEquals with const.

I would agree if there were a wide agreement out there that opEquals 
must have a const signature. In that case, the required signature should be:

bool opEquals(auto ref const T) const;

"auto ref", again, is NOT two templates into one, it's argument binding 
relaxation.

[snip]
>>> Has this sort of idea been explored? Is there something wrong with it?
>>
>> What's wrong with it is it consistently leads to suboptimal code,
>> which has created a hecatomb of problems for C++ (even the very
>> carefully conceived rvalue references feature, for all its size and
>> might, is unable to fix them all).
>>
>> The caller should create the copy and pass the responsibility of
>> destroying to the callee. This is because oftentimes the actual object
>> destroyed is not the same object that was constructed. You see those
>> trailing calls ~__tmp1, ~__tmp2 at the end of your code? They are a
>> tin cat stuck to code's tail.
>
> They need not apply to functions with 'inout' parameters. A parameter
> which is passed by 'inout' will either be used in the return value, or
> it will need to be destroyed.
> It seems clear to me that when you declare an 'inout' parameter, you're
> assuming responsibility for the lifetime of the object.

I'm not sure I understand, sorry. To recap, "inout" used to mean "ref" 
but not anymore. It just means "this stands for either const, immutable, 
or nothing". I'm not sure how that affects caller's responsibility.

And to clarify: due to D's rule that all structs are moveable, the 
object against which the destructor will be called may be different than 
the one against which the constructor was called. Therefore, the rule 
that the creator code is always responsible for destruction is not 
applicable.


Andrei


More information about the Digitalmars-d mailing list