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