2 ms·
Ah right, that. To be clear to others: the problem appears due to the fact that `R (Base::*)(A...)` can be cast to `R (Derived::*)(A...)` (note the contravaria
by o11c 3y ago
Ah right, that.
To be clear to others: the problem appears due to the fact that `R (Base::*)(A...)` can be cast to `R (Derived::*)(A...)` (note the contravariance), not just at construction time but arbitrarily later. This is unfortunately special to the `this` argument; a sane language should allow contravariance of any function pointer argument. (this implies that, if you're emitting C code, all arguments should unfortunately be `Object ` to avoid illegal casts.
I actually disagree that closures fix this though - you can easily end up with deeply nested closures which are opaque so you can't optimize them out. Rather, just make unbound functions identical to free functions.
And yes, I agree that single-inheritance + traits (~ late interfaces, ~ external vtables, which turn out to be easier to think about inlining. You can't use instanceof though) is much saner than multiple-inheritance + ADL. Honestly, I'd say that ADL is C++'s worst mistake, since it intrudes upon every* function call you make, unlike most of the crazy stuff we're doing here.
Doing without single-inheritance is in fact viable but annoying, especially when porting code or programmers. Emulating single-inheritance in terms of traits can be done by splitting each class into a concrete struct + a trait (and aggregating the structs but inheriting the traits), but this requires manually forwarding all the unchanged virtual functions, and also trait pointers are fat which is often undesirable (except note that for small types you often don't want to pay for an embedded vtable pointer). Moving that back to a class inheritance system, however ... I've often thought it is quite useful to support both "pointer to class, virtually" and "pointer to class, final".
- int_19h 3y ago> I actually disagree that closures fix this though - you can easily end up with deeply nested closures which are opaque so you can't optimize them out. Rather, just make unbound functions identical to free functions. It would be trivial to optimize - as that would be a lambda of the form `[](Foo& foo, ...) { foo.bar(...); }` it can just be emitted as a free function with no closure, no matter how deeply nested it is. In fact, looking at it in Godbolt, it seems that compilers do this even when optimizations aren't enabled at all, probably because it's just the most obvious way to codegen. Effectively, it just becomes a way to define a regular function in-place at the point where its address gets taken. In C++, such a lambda is even implicitly convertible to the corresponding plain function pointer type, so it's already unified with regular free functions on type system level. Of course, you could then say that &Foo::bar is simply syntactic sugar for such a lambda. I think we agree that the important part here is to avoid having the special notion of pointers-to-member-functions in the type system; the rest is just a question of how to dress it up when you have a use case where you need one.