Ignoring polymorphism, typeid() gives you an object representing the static type of the expression. But there are certain elements that are ignored when it comes to expression types. From [expr]:
If an expression initially has the type “reference to T” (8.3.2, 8.5.3), the type is adjusted to T prior to
any further analysis. [...] If a prvalue initially has the type “cv T”, where T is a cv-unqualified non-class, non-array type, the type of
the expression is adjusted to T prior to any further analysis.
As a result, any types which differ only in top-level cv-qualification or reference will yield the same typeid. For instance, the types int, const int, int& volatile const int&&, etc all give you the same typeid().
Basically, your initial thought process was:
typeid(T) == typeid(U) <==> std::is_same<T, U>
But the correct equivalence is:
typeid(T) == typeid(U) <==> std::is_same<expr_type<T>, expr_type<U>>
where:
template <class T>
using expr_type = std::remove_cv_t<std::remove_reference_t<T>>;