Date: Wed, 27 Jan 2021 10:22:25 +0000
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<https://eur02.safelinks.protection.outlook.com/?url=https%3A%2F%2Fwww.hexagonmi.com%2Ffr-FR%2F&data=02%7C01%7C%7C9382ad0642f34df9564908d8007bc6c4%7C1b16ab3eb8f64fe39f3e2db7fe549f6a%7C0%7C0%7C637259878894792630&sdata=VS4%2B3Oc4yGnnDiF7m8x%2BiD6u5GnRjS634jv1suVUhrU%3D&reserved=0>
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<https://eur02.safelinks.protection.outlook.com/?url=https%3A%2F%2Fwww.hexagonmi.com%2Ffr-FR%2F&data=02%7C01%7C%7C9382ad0642f34df9564908d8007bc6c4%7C1b16ab3eb8f64fe39f3e2db7fe549f6a%7C0%7C0%7C637259878894792630&sdata=VS4%2B3Oc4yGnnDiF7m8x%2BiD6u5GnRjS634jv1suVUhrU%3D&reserved=0>
Received on 2021-01-27 04:22:31