On Sat, Jul 16, 2022 at 11:12 AM Nikl Kelbon via Std-Proposals <std-proposals@lists.isocpp.org> 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