Is casting std::pair<T1, T2> const& to std::pair<T1 const, T2> const& safe?
Is it safe (in theory or in practice) to reinterpret_cast
a std::pair<T1, T2> const &
into a std::pair<T1 const, T2> const &
, assuming that the programmer hasn't intentionally done something weird like specializing std::pair<T1 const, T2>
?
It's NOT portable to do so.
std::pair
requirements are laid out in clause 20.3. Clause 17.5.2.3 clarifies that
Clauses 18 through 30 and Annex D do not specify the representation of classes, and intentionally omit specification of class members. An implementation may define static or non-static class members, or both, as needed to implement the semantics of the member functions specified in Clauses 18 through 30 and Annex D.
This implies that it's legal (although incredibly unlikely) for an implementation to include a partial specialization such as:
template<typename T1, typename T2>
struct pair<T1, T2>
{
T1 first;
T2 second;
};
template<typename T1, typename T2>
struct pair<const T1, T2>
{
T2 second;
const T1 first;
};
which are clearly not layout-compatible. Other variations including inclusion of additional non-static data members possibly before first
and/or second
are also allowed under the rule.
Now, it is somewhat interesting to consider the case where the layout is known. Although Potatoswatter pointed out DR1334 which asserts that T
and const T
are not layout-compatible, the Standard provides enough guarantees to allow us to get most of the way anyway:
template<typename T1, typename T2>
struct mypair<T1, T2>
{
T1 first;
T2 second;
};
mypair<int, double> pair1;
mypair<int, double>* p1 = &pair1;
int* p2 = reinterpret_cast<int*>(p1); // legal by 9.2p20
const int* p3 = p2;
mypair<const int, double>* p4 = reinterpret_cast<mypair<const int, double>*>(p3); // again 9.2p20
However this doesn't work on std::pair
as we can't apply 9.2p20 without knowing that first
is actually the initial member, which is not specified.
pair
is defined in section 20.3.2 of the standard to have data members:
template <class T1, class T2>
struct pair {
T1 first;
T2 second;
};
This means that for concrete types T1
, T2
, pair<T1, T2>
and pair<const T1, T2>
are guaranteed to have respective data members:
struct pair<T1, T2> {
T1 first;
T2 second;
};
struct pair<const T1, T2> {
const T1 first;
T2 second;
};
Now, if T1
and T2
are both standard-layout, then pair<T1, T2>
and pair<const T1, T2>
are both standard-layout. As discussed above, by DR1334 they are not layout-compatible (3.9p11), but by 9.2p19 they can be reinterpret_cast
to their respective T1
or const T1
first member. By 9.2p13 the T2
second member must be located after the first member (i.e. with higher address) and by 1.8p5 must be located immediately after the first member such that the object is contiguous after accounting for alignment (9.2p19).
We can check this using offsetof
(which is defined for standard-layout types):
static_assert(offsetof(pair<T1, T2>, second) ==
offsetof(pair<const T1, T2>, second), "!");
Since pair<T1, T2>
and pair<const T1, T2>
have the same layout, casting in the forward direction and using the result to access the members is valid by 3.9.2p3:
If an object of type
T
is located at an addressA
, a pointer of type cvT*
whose value is the addressA
is said to point to that object, regardless of how the value was obtained.
So the reinterpret_cast
is safe only if std::is_standard_layout<std::pair<T1, T2>>::value
is true
.