Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 567
user541686 Avatar asked Jan 11 '13 05:01

user541686


People also ask

Is std :: pair contiguous?

std::pair is a struct, the standard says the compiler determines the layout though the order must be maintained, so in the instance of std::pair<char,char> your compiler may decide to place 3-byte padding after each char for optimal alignment, so no you can't assume contiguous memory layout - end of story.

What is std :: pair in C++?

std::pair is a class template that provides a way to store two heterogeneous objects as a single unit. A pair is a specific case of a std::tuple with two elements.


2 Answers

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.

like image 109
Ben Voigt Avatar answered Sep 24 '22 05:09

Ben Voigt


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.

like image 35
ecatmur Avatar answered Sep 22 '22 05:09

ecatmur