C++ Logo

std-proposals

Advanced search

Re: [std-proposals] Flat Dynamic Polymorphism Library

From: Nikl Kelbon <kelbonage_at_[hidden]>
Date: Sat, 16 Jul 2022 22:16:23 +0500
There are way to do 'drop-replace', its optional and not described in
proposal ( .... )
https://godbolt.org/z/1b8zM5dr3
In this case you can use 'explicit interface' (other feature from there) to
ban creating from another const/poiy_ref or another any_with
> with just those 6 lines of boilerplate
Without, firsly because any_with<Methods...> has inner aliases for ref/
ptr/ const_ref/ const_ptr, and second because const/poly_ref designed to
used in function signatures, so you can explicitly indicate what the
function needs and all poly_refs which has those methods will be implicitly
converted when invoking



сб, 16 июл. 2022 г. в 21:36, Arthur O'Dwyer <arthur.j.odwyer_at_[hidden]>:

> On Sat, Jul 16, 2022 at 11:12 AM Nikl Kelbon via Std-Proposals <
> std-proposals_at_[hidden]> wrote:
>
>> Thank you for your answer, I will try to answer briefly the first
>> questions that have arisen
>> > What you're calling "flat dynamic polymorphism" is what is typically
>> referred to as "type erasure."
>> No, it's implemented with type erasure, but it's not a type erasure.
>>
>
> I think the big difference between this and classic type erasure is that,
> here, the thing you end up with (a `const_poly_with<Draw>` object) doesn't
> itself afford a `.draw()` operation, so it's not a drop-in replacement for
> the original `T`. The writer of a polymorphic function must explicitly
> choose one of these two syntaxes:
>
> // (A), static polymorphism with templates
> template<class Drawable>
> void option_a(const Drawable& d) {
> d.draw(std::cout); // syntax A
> }
>
> // (B), dynamic polymorphism with this new thing
> void option_b(const_poly_ref<Draw> d) {
> d.invoke<Draw>(std::cout); // syntax B
> }
>
> You cannot pass a `const_poly_ref<Draw>` to `option_a`; it won't compile.
> Whereas the STL types that do type erasure are specifically designed so
> that they can drop-in-replace the original `T` in generic code:
>
> // (A), static polymorphism with templates
> template<class Callable>
> void option_a(const Callable& f) {
> f(std::cout); // syntax A
> }
>
> // (B), dynamic polymorphism with std::function / any / unique_function /
> function_ref
> void option_b(std::function<void(std::ostream&)> f) {
> f(std::cout); // still syntax A (!!)
> }
>
> In general, what your design does here is actually a *good* thing —
> generally, it's *helpful* to give different syntaxes to "the part you
> customize in the callee" versus "the part you call from the caller."
>
> https://quuxplusone.github.io/blog/2018/03/19/customization-points-for-functions/
> One benefit you get from your current design is that it's obvious that
> Square s;
> const_poly_ref<Draw> r = s;
> any_with<Draw> a = r; // oops! error!
> should not compile, because `r` does not actually afford a `.draw()`
> method. (It affords only syntax B, `aa::invoke<Draw>(...)`.) On the other
> hand, the Standard Library's reference_wrapper and the upcoming
> function_ref permit us to write things like
> auto f = [](){};
> std::function_ref<void()> r = f;
> std::function<void()> a = r; // oops! captures a reference to `f`
> instead of a copy of `f`!
> auto r2 = std::ref(f);
> std::function<void()> b = r2; // oops! captures a copy of the
> reference_wrapper instead of a copy of `f`!
> because `r` and `r2` both *do* afford the syntax `r()`. So you get into a
> whole mess about what ought to happen when you try to implicitly convert a
> function_ref into a function_ref, or a function into a slightly different
> function, or a function pointer into a function_ref...
> int f();
> std::function<int()> a = f;
> std::function<long()> b = a; // this std::function type-erases a copy
> of `a`, which type-erases a pointer to `f`
> std::function<int()> c = b; // this std::function type-erases a copy
> of `b`, which type-erases a copy of `a`, which type-erases a pointer to `f`
> int (*pf)() = f;
> std::function_ref<int()> d = pf; // does this capture a reference to
> `pf`, or a reference to `f` itself?
> pf = g;
> d(); // does this call `g`, or `f`?
> With your library design, you don't run into any of these questions.
> `const_poly_ref<A>` does not implicitly convert to `any_with<A>`, nor even
> to `const_poly_ref<SlightlyDifferentA>`. This makes it less ergonomic to
> use (IMHO), but it does sidestep a lot of irritating philosophical issues.
>
>
> What about examples, pdf presents some links to godbolt with full examples
>> and my version of dyno 'Drawable' here :
>> https://godbolt.org/z/8YxPncEqE
>>
>
> FWIW, that was very useful to me. :)
> However, I still doubt the overall idea here. It's cool that the
> programmer can get almost-type-erasure in several different flavors
> (poly_ref, const_poly_ref, any_with,...) with just those 6 lines of
> boilerplate. But if the programmer knows *which* flavor they want, they
> can get that specific flavor in <20 lines of boilerplate today — not
> significantly more, in my opinion — and they can make their type a drop-in
> replacement for the original `T`, too, so they don't have to use the weird
> `aa::invoke<Draw>` syntax.
> https://godbolt.org/z/zqPhxndjK
> But observe that this objection/skepticism of mine isn't at all specific
> to *your* library; I'd probably say the same thing about Dyno and
> Boost.TypeErasure and the rest. Your library might fill the same niche for
> people who are already in the target audience of those libraries. I just
> continue to fail to understand who those people are.
>
> my $.02,
> Arthur
>

Received on 2022-07-16 17:16:36