On Tue, Jan 19, 2021 at 4:22 AM Yves Bailly via Std-Proposals <std-proposals@lists.isocpp.org> wrote: 

From: Std-Proposals <std-proposals-bounces@lists.isocpp.org> On Behalf Of Arthur O'Dwyer via Std-Proposals
Sent: Friday, January 15, 2021 17:11
To: Std-Proposals <std-proposals@lists.isocpp.org>
Cc: Arthur O'Dwyer <arthur.j.odwyer@gmail.com>
Subject: Re: [std-proposals] Explicit using

On Fri, Jan 15, 2021 at 10:54 AM Yves Bailly via Std-Proposals <std-proposals@lists.isocpp.org> 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"?

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