30:00 Yes, you can have operator[] without references. D does it like this: x = obj[i] and obj[i] = x lower to two different function calls. Expressed in C++ syntax, D has operator[]= and operator[]+=, etc. which take the right-hand side as their first argument and the indices (there will be many, cf. C++23) following it: void operator[]+=(T rhs, size_t i) { … } One other advantage is that map’s operator[] creates an empty object and overwrites it when you do myMap[key] = value; - but if it’s one operation, no such nonsense is necessary. D does have syntax for return by reference and pass by reference, but no reference types.
The name "observer" for a pointer should not allow mutation. By nature observe does not mean change, and thus the implication is that it is a view. Observers should only ever be able to return const by .get() or * or whatever. Having .get() return a raw ptr implies the ability to do something other than observe. All the mutation related stuff should be handled by something else. A better name for something that can mutate would be non_owner_ptr, and for that it would be nice (although impossible) to somehow disable deletion of the underlying resource. Also, disable get() of the underlying resource. Basically all mutations must occur through the pointer * dereference unless someone does something unsafe like store the underlying with &*. non_owner_ptr should be implicitly constructible from unique_ptr. The desire to enable easy conversion to legacy code is once again causing bad core language decisions.
27:00 Established terms (e.g. Robert Ward's classic "Debugging C" from the 80's) are Lexical, Temporal, and Referential *Proximity* . "Non-local Reasoning" means you have low proximity, and the type of causality can be specified. "Further away" probably means lexical. Smart editors will show you the declaration/definition for a symbol under the cursor, so Referential Proximity shouldn't care about how many lines of code are between them or whether they are different files.
@37:30 The construction with the "assignment syntax" works, because it does copy initialization. Copy initialization does ignore explicit constructors. Your deleted constructor with a rvalue unique_ptr is marked explicit, so its not considered during overload resolution. We end up using the constructor with the unique_ptr const& parameter. Removing the explicit keyword will make the marked line fail (as wished). This also means that you wont be able to pass rvalues of unique_ptrs to functions taking the borrowed ref, since this construction also relay on implicit conversion (which now will fail aswell (and this makes somewhat sense); see line 286 and 287 in godbolt).
It's amazing how many times "always pass out arguments by pointer" has to be re-iterated, yet we keep running into people who haven't learned it yet. I think C# got this right by using explicit keywords...
17:40: Getting an observer implicitly from a unique_ptr seems natural, but must be done carefully. The observers are only valid as long as the owner is present (or some owner, for shared_ptr). Therefore, I want to see where observers are created! Image a function _f_ that returns a unique_ptr and another, _g,_ that takes oberserver_ptr. I don’t know how life-time of temporaries is extended exactly, but g(f()) looks innocent, but g(f().get()) will stand out in code review.
One of the most underrated talks
30:00 Yes, you can have operator[] without references. D does it like this: x = obj[i] and obj[i] = x lower to two different function calls. Expressed in C++ syntax, D has operator[]= and operator[]+=, etc. which take the right-hand side as their first argument and the indices (there will be many, cf. C++23) following it:
void operator[]+=(T rhs, size_t i) { … }
One other advantage is that map’s operator[] creates an empty object and overwrites it when you do myMap[key] = value; - but if it’s one operation, no such nonsense is necessary.
D does have syntax for return by reference and pass by reference, but no reference types.
The name "observer" for a pointer should not allow mutation. By nature observe does not mean change, and thus the implication is that it is a view. Observers should only ever be able to return const by .get() or * or whatever. Having .get() return a raw ptr implies the ability to do something other than observe. All the mutation related stuff should be handled by something else. A better name for something that can mutate would be non_owner_ptr, and for that it would be nice (although impossible) to somehow disable deletion of the underlying resource. Also, disable get() of the underlying resource. Basically all mutations must occur through the pointer * dereference unless someone does something unsafe like store the underlying with &*. non_owner_ptr should be implicitly constructible from unique_ptr. The desire to enable easy conversion to legacy code is once again causing bad core language decisions.
I agree. Observer implies read-only so returning 'T *' is a no-no. Same for having 'any_t * release()' in the API (14:48).
27:00 Established terms (e.g. Robert Ward's classic "Debugging C" from the 80's) are
Lexical, Temporal, and Referential *Proximity* .
"Non-local Reasoning" means you have low proximity, and the type of causality can be specified. "Further away" probably means lexical. Smart editors will show you the declaration/definition for a symbol under the cursor, so Referential Proximity shouldn't care about how many lines of code are between them or whether they are different files.
see the SGL for non_null that is orthogonal to what kind of pointer T is (raw, smart of some kind, whatever).
@37:30 The construction with the "assignment syntax" works, because it does copy initialization. Copy initialization does ignore explicit constructors. Your deleted constructor with a rvalue unique_ptr is marked explicit, so its not considered during overload resolution. We end up using the constructor with the unique_ptr const& parameter. Removing the explicit keyword will make the marked line fail (as wished). This also means that you wont be able to pass rvalues of unique_ptrs to functions taking the borrowed ref, since this construction also relay on implicit conversion (which now will fail aswell (and this makes somewhat sense); see line 286 and 287 in godbolt).
23:09 you can get around that with SFINAE
23:45 I didn’t try but maybe it works using templates and use enable_if or C++20 contracts to exactly select what is allowed and what’s not.
27:50 what is meant by . meaning two things and -> meaning one?
Whoa this talk really gets you thinking. borrow_ptr sounds suspiciously like borrow semantics from rust?
It's amazing how many times "always pass out arguments by pointer" has to be re-iterated, yet we keep running into people who haven't learned it yet. I think C# got this right by using explicit keywords...
17:40: Getting an observer implicitly from a unique_ptr seems natural, but must be done carefully. The observers are only valid as long as the owner is present (or some owner, for shared_ptr). Therefore, I want to see where observers are created! Image a function _f_ that returns a unique_ptr and another, _g,_ that takes oberserver_ptr. I don’t know how life-time of temporaries is extended exactly, but g(f()) looks innocent, but g(f().get()) will stand out in code review.
C++20 unfortunately does not have std::observer_ptr