`pair<Key, Value>` 和 `pair<const Key, Value>` 之间的类型双关

zjy*_*qs 5 c++ language-lawyer type-punning

一个相对的问题,但我想要一个没有任何运行时开销的解决方案。(所以构建一个新的pair或使用std::variant不是答案)


由于潜在的模板专业化,参考文献已经说过pair<K, V>pair<const K, V>不相似,这意味着简单的reinterpret_cast会触发未定义的行为。

auto p1 = pair<int, double>{ 1, 2.3 };
auto& p2 = reinterpret_cast<pair<const int, double>&>(p1); // UB!
Run Code Online (Sandbox Code Playgroud)

类型双关union在 C 中工作得很好,但在 C++ 中并不总是合法的:

但有一个例外(与 C 中的行为一致?):

如果两个联合成员是标准布局类型,则在任何编译器上检查它们的公共子序列都是明确定义的。

由于KeyandValue可能不是并且standard-layout可能有non-trivial析构函数,因此这里的类型双关似乎是不可能的,尽管pair<Key, Value>和的成员pair<const Key, Value>可以共享相同的生命周期(当然带有对齐断言)。

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));

Run Code Online (Sandbox Code Playgroud)

我注意到调用 时std::map::extract保证不会复制或移动任何元素,并且用户定义的规范化std::pair会在操作 时导致 UBNode handle。所以我相信一些类似的行为(类型双关或const_cast)确实存在于与 相关的 STL 实现中Node handle

在 中libc++,它似乎取决于clang不针对数据成员进行优化)的特征,而不是标准。

libstdc++做了与 类似的工作libc++,但没有std::launder刷新类型状态。

MSVC是......非常令人惊讶......并且提交历史太短,我找不到任何理由来支持如此简单的别名......


这里有标准方法吗?

Jef*_*ett 1

这是不可能的。这node_handle提案提到这一点作为标准化的动机:

\n
\n

标准库存在的原因之一是编写客户端可以用可移植 C++ 编写的不可移植且神奇的代码(例如 、 、 <type_traits> 等)。这只是另一个这样的例子。

\n
\n
\n

请注意,key成员函数是唯一需要此类技巧的地方,并且不需要对容器或容器对进行任何更改。

\n
\n

参考

\n