A relative question, but I want a solution without any run-time overhead. (So constructing a new pair
or using std::variant
are not the answers)
Due to the potential template specialization, reference has said pair<K, V>
and pair<const K, V>
are not similar, that means a simple reinterpret_cast
would trigger undefined behaviour.
auto p1 = pair<int, double>{ 1, 2.3 };
auto& p2 = reinterpret_cast<pair<const int, double>&>(p1); // UB!
Type-punning through union
works fine in C, but not always legal in C++:
But there's an exception (to be consistent with behaviours in C?):
Since Key
and Value
may not be standard-layout
and may have non-trivial
destructor, it seems type-punning here is impossible, though members of pair<Key, Value>
and pair<const Key, Value>
could share the same lifetime (of course with alignment assertion).
template <typename Key, typename Value>
union MapPair {
using TrueType = pair<Key, Value>;
using AccessType = pair<const Key, Value>;
static_assert(
offsetof(TrueType, first) == offsetof(AccessType, first)
&& offsetof(TrueType, second) == offsetof(AccessType, second)
&& sizeof(TrueType) == sizeof(AccessType)
);
TrueType truePair;
AccessType accessPair;
~MapPair() {
truePair.~pair();
}
// constructors for `truePair`
};
//...
auto mapPair = MapPair<NonTrivialKey, NonTrivialValue>{/*...*/};
// UB? Due to the lifetime of `truepair` is not terminated?
auto& accessPair = reinterpret_cast<pair<const NonTrivialKey, NonTrivialValue>&>(mapPair);
// still UB? Although objects on the buffer share the same constructor/destructor and lifetime
auto* accessPairPtr = std::launder(reinterpret_cast<pair<const NonTrivialKey, NonTrivialValue>*>(&mapPair));
I've noticed the guarantee that no elements are copied or moved when calling std::map::extract
, and user-defined specilization of std::pair
would cause UB when operating Node handle
. So I trust some similar behaviours (type-punning or const_cast
) really exist in the STL implementations relating to Node handle
.
In libc++
, it seems to depend on the characteristic of clang
(doesn't optimize for data members), not the standard.
libstdc++
did the similar work as libc++
, but no std::launder
to refresh the type state.
MSVC
is ... very surprising... and the commit history is too short that I can't find any reasons to support such a simple aliasing...
Is there a standard way here?
This is impossible. The node_handle
proposal mentioned this as a motivation for standardizing it:
One of the reasons the Standard Library exists is to write non-portable and magical code that the client can’t write in portable C++ (e.g. , , <type_traits>, etc.). This is just another such example.
Note that the
key
member function is the only place where such tricks are necessary, and that no changes to the containers or pair are required.