C++ Logo

std-proposals

Advanced search

Re: Explicit using

From: Arthur O'Dwyer <arthur.j.odwyer_at_[hidden]>
Date: Fri, 22 Jan 2021 12:03:23 -0500
On Tue, Jan 19, 2021 at 4:22 AM Yves Bailly via Std-Proposals <
std-proposals_at_[hidden]> wrote:

> *From:* Std-Proposals <std-proposals-bounces_at_[hidden]>
> <std-proposals-bounces_at_[hidden]> *On Behalf Of *Arthur O'Dwyer
> via Std-Proposals
> *Sent:* Friday, January 15, 2021 17:11
> *To:* Std-Proposals <std-proposals_at_[hidden]>
> <std-proposals_at_[hidden]>
> *Cc:* Arthur O'Dwyer <arthur.j.odwyer_at_[hidden]>
> <arthur.j.odwyer_at_[hidden]>
> *Subject:* Re: [std-proposals] Explicit using
>
> On Fri, Jan 15, 2021 at 10:54 AM Yves Bailly via Std-Proposals <
> std-proposals_at_[hidden]> wrote:
>
>
> First, the "stronger type alias":
> using U = new T;
>
> As you've realized (good!), the problems are going to be with T's
> existing customization points:
>
> - std::swap(u, u)
> - std::hash<T>(u)
>
> using Name = new std::string;
> void Store(std::string the_name); // (1)
> void Store(Name the_name); // (2)
> /* error */ Store("Your name here"); // ambiguous, ill-formed
>
> FWIW, this is mildly surprising; this would be a place where
> `std::is_base_of_v<T, U>` and yet the signature `void(U)` is not considered
> more-specialized-than `void(T)`.
>
> In fact it's precisely the point and goal of this proposal: to avoid
> potential surprises.
>
> As there's an implicit conversion from char const* to std::string *and*
> to Name (this one "inherited" from std::string), then as a mere code
> reader I'm not sure which one Store() would be called here. As a mere
> code writer I may have an idea which may well be wrong. So here the
> suggestion is to declare the line ill-formed as ambiguous, because we have
> here a kind of "two-levels" implicit conversion, which is getting difficult
> to track.
>
> Therefore the writer would have to make the intent explicit. After all
> it's the goal of strong typing: make things explicit. It seems to me it's
> also a general trend in C++ last years.
>
> From what I can see, a well-known (to the "initiated") situation like this:
>
> void foo(bool b);
> void foo(std::string s);
> foo("bar"); // which one is called?
>
> ...is really confusing for many developers, even experieced ones. Most
> developers are "just" language users, not language lawyers :-)
>
> But I think this is just an indication that your intuition about
> std::is_base_of is wrong. Types with no inheritance relationships (no base
> classes) definitely should *not* claim to have is_base_of relationships.
>
> I see the point here (I think). So indeed std::is_base_of_v<T,U> should
> be false. Probably std::underlying_type<> is enough to be able to query
> the relationship between T and U.
>
> /* ok */ using Names_Income = std::unordered_map<Name, double>;
> // std::hash<std::string> used because of
> // implicit cast from Name to std::string
>
> If there's an implicit conversion from Name to std::string, then why did
> you say
>
> std::string s;
> s = name;
>
> didn't work?
>
> I don't think I said that, on the contrary... or did I miss something?
>
> Or are you refering to the second form called "strict type alias"?
>
> See
>
>
> https://quuxplusone.github.io/blog/2018/06/12/perennial-impossibilities/#so-wouldn-t-it-be-cool-if-we-had
> <https://eur02.safelinks.protection.outlook.com/?url=https%3A%2F%2Fquuxplusone.github.io%2Fblog%2F2018%2F06%2F12%2Fperennial-impossibilities%2F%23so-wouldn-t-it-be-cool-if-we-had&data=04%7C01%7C%7C3b2d768c8d7e4c90c44708d8b97047b7%7C1b16ab3eb8f64fe39f3e2db7fe549f6a%7C0%7C0%7C637463239180470720%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C1000&sdata=se8F77I73Q9jvjAD%2FWbjRa2bSKE79oixeMwjcHAElaI%3D&reserved=0>
>
> Nice page I saw but forgot about :-)
>
> Let me try to answer your questions, giving an answer for both suggested
> cases ("new T" and "explicit T"):
>
> A: yes / yes
> B: yes / no
> C: yes / no
> D: yes / no
> E: yes / no
> F: yes / yes
> G: yes / yes
> H: yes / no
> I: yes / no
>
(Focusing only on the left-hand, "new T" syntax; ignoring "explicit T" in
order to cut down on distractions)
I realize now that I could have made those questions even harder by having
some signatures with *multiple* Widget parameters, e.g.
    using U = new T;
    void assign(T& dest, T& src);
    T t;
    U u;
    t = u; // you'd say this is OK, I assume? you do say "there's an
implicit conversion from U to T"
    u = t; // you'd say this is not OK, I assume? you do say "but there's
no implicit conversion from T to U"
    assign(t, t); // OK in standard C++
    assign(u, u); // you'd say this is OK, I assume? you do say "any
function which takes a T can also take a U"
    assign(t, u); // is this OK? you say "any function which takes a T can
also take a U"
    assign(u, t); // is this OK? you say "any function which takes a T can
also take a U"
This seems to produce inconsistent results, where `u = t` is not OK but
`assign(u, t)` is OK — or what happens here?

Consider what (if anything) would change if assign's signature were one or
the other of these:
    void assign(T& dest, std::type_identity_t<T>& src);
    void assign(std::type_identity_t<T>& dest, T& src);

I don't intuitively understand how you propose to answer "yes" to question
E. How does `std::hash<U>` get created by the compiler? (I'm not sure it's
impossible, but you need to flesh out this mechanism, because it feels like
"magic.")

Suppose we have
    struct T {
        using self = T;
        T(const self&) = default; // copy constructor
    };
    using U = new T;
    static_assert(std::is_same_v<U::self, ???>);
Is `U::type` equal to `T` or `U`? Why?
Does `U` have a defaulted copy constructor, an ill-formed constructor from
`const T&`, a well-formed constructor from `const T&`, or what? Why?

–Arthur

Received on 2021-01-22 11:03:38