Here is the rule ([basic.lval]/8) in its C++17 form, but it looks similar in the other standards ("lvalue" instead of "glvalue" in C++98):
8 If a program attempts to access the stored value of an object through a glvalue of other than one of the following types the behavior is undefined:
(8.4) — a type that is the signed or unsigned type corresponding to the dynamic type of the object
The rule sounds like "You'll have UB, unless you do X", but this does not mean that if you do X, you won't get UB, as one might expect! And indeed, doing X is conditional or unconditional UB, depending on the version of the standard.
Lets look at the following code:
int i = -1;
unsigned j = reinterpret_cast<unsigned&>(i);
What is the behavior of this code?
C++98 and C++11
[expr.reinterpret.cast]/10 (/11 in C++11) (emphasis is mine):
An lvalue expression of type T1 can be cast to the type “reference to T2” if an expression of type “pointer to T1” can be explicitly converted to the type “pointer to T2” using a reinterpret_cast. That is, a reference cast reinterpret_cast(x) has the same effect as the conversion *reinterpret_cast(&x) with the built-in & and * operators. The result is an lvalue that refers to the same object as the source lvalue, but with a different type.
So reinterpret_cast<unsigned&>(i) lvalue refers to the int object i, but with usigned type. Initialization needs the value of the initializing expression, which formally means that the lvalue-to-rvalue conversion is applied to the lvalue.
[conv.lval]/1:
An lvalue of a non-function, non-array type T can be converted to an rvalue. If T is an incomplete type, a program that necessitates this conversion is ill-formed. If the object to which the lvalue refers is not an object of type T and is not an object of a type derived from T, or if the object is uninitialized, a program that necessitates this conversion has undefined behavior.
Our lvalue of unsigned type does not refer to an object of unsigned type which means that the behavior is undefined.
C++14 and C++17
In these standards the situation is a bit more complicated, but the rules have been slightly relaxed. [expr.reinterpret.cast]/11 tells the same:
The result refers to the same object as the source glvalue, but with the specified type.
The offending wording about UB has been deleted from [conv.lval]/1:
A glvalue of a non-function, non-array type T can be converted to a prvalue. If T is an incomplete type, a program that necessitates this conversion is ill-formed. If T is a non-class type, the type of the prvalue is the cv-unqualified version of T. Otherwise, the type of the prvalue is T.
But which value the L-to-R conversion reads? [conv.lval]/(2.6) (/(3.4) in C++17) answers this question:
… the value contained in the object indicated by the glvalue is the prvalue result
unsigned lvalue reinterpret_cast<unsigned&>(i) indicates the i int object with the value -1 and the prvalue resulting from L-to-R conversion has unsigned type. [expr]/4 says:
If during the evaluation of an expression, the result is not mathematically defined or not in the range of representable values for its type, the behavior is undefined.
-1 is definitely is not in the range of representable values for the unsigned type of the prvalue expression, so the behavior is undefined.
The behavior would be defined if the i object contained a value from the [0, INT_MAX] range.
The same reasoning is applicable in the case when an unsigned object is accessed through an int glvalue. This is UB in C++98 and C++11 and UB in C++14 and C++17 unless the value of the object in the [0, INT_MAX] range.
So, in contrast to a popular belief that this aliasing rule allows to reinterpret an object as containing a value of corresponding signed/unsigned type, it does not allow it. For the values in [0, INT_MAX] range, objects of signed and unsigned types have the same representation ("The range of non-negative values of a signed integer type is a subrange of the corresponding unsigned integer type, the representation of the same value in each of the two types is the same" says [basic.fundamental]/3 in C++17). It is hard to call such an access a "reinterpretation", not to mention this was unconditional UB prior to C++14.
What is the purpose of the rule ([basic.lval]/(8.4)) then?