C++ Logo

std-proposals

Advanced search

Re: [std-proposals] How should we tag a class? Use an empty base class or a typedef?

From: Arthur O'Dwyer <arthur.j.odwyer_at_[hidden]>
Date: Wed, 29 May 2024 09:58:13 -0400
On Mon, May 27, 2024 at 7:55 PM Gašper Ažman <gasper.azman_at_[hidden]> wrote:

> Arthur,
>
> The whole reason to even have std::is_elide_v separate is because you
> can't necessarily change your own widget. Like, I have one called
> initializer_t, I want to use it as if it were std::elide, but I can't fix
> it to have the nested typedef, perhaps because I don't have write access.
>

You do? My understanding of Frederick's `std::elide` idea was that it would
be basically the same thing that I call "SCSE", i.e.:
https://godbolt.org/z/T7W3GW35h

    template<class T, class F>
    struct elide {
      F lam_;
      explicit elide(F lam) : lam_(lam) {}
      operator T() const { return lam_(); }
    };
    elide(auto&& lam) -> elide<decltype(lam()), decltype(lam)>;

    template<class T>
    struct list {
      template<class... Args> void emplace_back(Args&&...);
    };
    std::list<Immovable> v;
    Immovable make_immovable();
    v.emplace_back(elide([]() { return make_immovable(); }));

and then we have to postulate that there's an ImmovableAny like
https://godbolt.org/z/nh7ehcK9o
    struct ImmovableAny {
      template<class T> requires (!std::is_same_v<std::decay_t<T>,
ImmovableAny>)
        ImmovableAny(T&&);
      ImmovableAny(ImmovableAny&&) = delete;
    };
that we want to emplace like this:
    std::list<ImmovableAny> v;
    ImmovableAny make_immovable();
    v.emplace_back(elide([]() { return make_immovable(); }));
and then *the problem we're trying to solve* is that this will construct an
`ImmovableAny` holding an `elide`, rather than calling `elide::operator
ImmovableAny`. Frederick's proposed *solution* (which, as I said, IMO is
very bad) is that template type deduction should never deduce a (possibly
cvref-qualified) specialization of `elide`.

So you have code that uses a third-party `initializer_t` that acts just
like this `elide`? and since it's third-party you can't modify it, nor can
you rewrite it to match the seven-liner above? but you do want that
third-party `initializer_t` to share this template-type-deduction carveout
in the core language, because you want to use types like `ImmovableAny`?

I mean, I guess there's nothing *logically inconsistent* about any of that
scenario. It just *feels* like we've gone two or three levels beyond
realism at this point.

---
In general, the type author should be in control of their type's behavior,
and users shouldn't be allowed to ODR-violate that type *directly*. The
accepted way to "modify" a type's behavior is to wrap it in a different
type — with a different name for ODR purposes — and apply all your new
behaviors to that new type.
Two examples:
- P1144 would have permitted taking a third-party type that you can't
modify (like boost::static_vector) and — not* directly warranting* it
(which would violate ODR for functions like `vector<static_vector>::erase`)
— but *wrapping* it in your own rule-of-zero type which you *can* warrant.
[P2786 won't allow this.]
- In 2023, LWG finally confirmed that ordinary programmers aren't allowed
<https://github.com/cplusplus/draft/pull/6134> to specialize
`allocator_traits` for their own types. The right place to customize the
behavior of an allocator `A` is inside `A` itself, not inside
`allocator_traits<A>`. If you don't own `A`, then you must wrap it in `B`
and customize `B` instead.
my $.02,
Arthur

Received on 2024-05-29 13:58:28