5

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

If two union members are standard-layout types, it's well-defined to examine their common subsequence on any compiler.

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?

zjyhjqs
  • 609
  • 4
  • 11
  • Why do you want this at all? What is it you are trying to implement? – Goswin von Brederlow Jun 18 '22 at 16:44
  • @GoswinvonBrederlow `Node handle` in STL, or a map container like `SortedList` in C#. And I don't want to break the API compatibility (like pair) – zjyhjqs Jun 18 '22 at 16:48
  • 3
    The standard library isn’t constrained to use well-defined code as it can collude with the compiler. – Davis Herring Jun 18 '22 at 18:09
  • Still makes no sense. Anything in the map must have a `const K` or your map can get corrupted from the outside. So if you hand out handles they must be `pair`. So just make everything `const K` so you don't even get internal corruption by accident. – Goswin von Brederlow Jun 19 '22 at 01:14
  • @GoswinvonBrederlow Even node with `const K` still could get internal corruption by accident: `node._pair.~pair(); new(&(node.pair)) pair{wrongKey, value}`. And note that `Node handle` has no relation to its original container, it's an extracted one to be inserted into a new container, so its state should be modifiable. – zjyhjqs Jun 19 '22 at 01:49
  • Calling the destructor and then placement new you hardly do by accident. So finally we come to the real problem: You want to implement a `NodeHandle extract(Iterator)` like method? This is really unclear in your question. – Goswin von Brederlow Jun 19 '22 at 02:06
  • @GoswinvonBrederlow Actually my question is just about if there's some workarounds about the behaviour. And in STL, Node handle is the return type of methods like [`std::map::extract`](https://en.cppreference.com/w/cpp/container/map/extract). A detailed `Node handle`'s definition is [here](https://en.cppreference.com/w/cpp/container/node_handle). – zjyhjqs Jun 19 '22 at 02:21

1 Answers1

1

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.

ref

Jeff Garrett
  • 5,863
  • 1
  • 13
  • 12