I think I get your point. After more thinking about it (which I should have done before, sorry for that), then in short, I would say when writing
using U = new T;
…then yes, U is a new type different from T – although referring to the same Platonic type as
T.
This implies that indeed, std::is_same<T,U> is different from
std::is_same<T,T> and std::is_same_v<T,U> == std::is_same_v<U,T> == false.
> For example:
> template<class A> void f(A a);
> f(T{}); // #1
> f(U{}); // #2
> Does #1 call f<T>? (Yes, it must.)
Yes.
> Does #2 call f<U>? (I don't know but I think so.)
Yes, because it’s given a U.
> Is f<U> the same function as, or a different function from, f<T>? (I don't know but I think so.)
If T and U are two different types, then f<U> is a different function from
f<T>.
However, because U and T refer to the same Platonic type and a
U can be seen as a T, then if f<U> is syntactically correct, well-formed with regard to the restrictions put on U (for example, inside
f<> there’s no assignment of a
T to a U without an explicit cast), then the actual instantiation of
f<U> can be the same as the instantiation of
f<T> - in a way, f<U> = f<T>. Put in another way, if we assume
f<T> has already been created, then the compiler would create a new “envelop” named
f<U> but with the same contents as
f<T> – unless of course some explicit specialization
f<U> is provided by the user.
This would apply to the std::hash<> specialization: when required to instantiate
std::hash<U>, if it has not been explicitly specialized, then as you said the compiler may realize that
std::hash<U> is the same (has the same contents although it doesn’t have the same identity) as
std::hash<T> - again, if and only if the code for
std::hash<U> is well-formed and there’s no explicit specialization defined provided by the user.
> using Height = new int;
> using Tilt = new int;
> void increaseAltitude(Height h);
> void increaseAttitude(Tilt t);
> ...
> Height h;
> increaseAttitude(h); // oops — but, since increaseAttitude takes a Tilt, this happily won't compile!
>
> However, it doesn't seem to protect us against
> Height h;
> Tilt t;
> t += h; // oops — and this silently calls the built-in candidate operator+=(int&, int)
That’s true indeed, no more protection here than when using “traditional” typedefs. And I see how this can indeed be seen as a “massive inconsistency” between
t=h (not OK) and t+=h (OK).
However, I would argue the situation is still better than when using “standard” using declarations: at least the function call typo will be caught.
If protection in above situations is desired, a solution would be to explicitly delete the operations we don’t want:
Tilt& operator+=(Tilt&,Height const&) = delete;
This would protect against the code you suggested, but the following would still be valid:
int h2; // “int”, not “Height”
t += h2; // OK – and maybe oops, or not
Another solution would be to use the other suggested construct:
using Height = explicit int;
using Tilt = explicit int;
Height h{1}; // OK
Tilt t{2}; // OK
t += 1; // OK – literal used
t += h; // ill-formed here thanks to the “explicit”
With the “explicit” construct, there’s no implicit interaction between T and U.
Regards,
Yves Bailly
Development engineer
Manufacturing Intelligence division
Hexagon
M: +33 (0) 6.82.66.09.01
HexagonMI.com