Is casting std::pair const& to std::pair c

2019-01-11 13:53发布

问题:

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>?

回答1:

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.



回答2:

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 address A, a pointer of type cv T* whose value is the address A 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.



回答3:

The practical answer is that casting to const should be safe since you are reinterpret-casting to an object with an identical representation. However, the other way around introduces undefined behaviour (const to non-const).

As for the "theoretical" answer, I should note that the C++ standard does not guarantee an identical bitwise representation of const/non-const objects. The const keyword guarantees "conceptual constness", which is implementation dependant.