tl;dr 这是一系列非常长的黑客攻击的结果,以实现疯狂的重载/显式规则std::pair
并保持 ABI 兼容性。这是 C++20 中的一个错误。
免责声明
这更像是与标准库作者一起沿着记忆之路进行的“有趣”之旅,然后是一些富有洞察力的语言级别启示。它显示了 C++ 的实现变得多么复杂pair,这是一项艰巨的任务。
我尽力重现历史,但我不是作者之一。
配对底漆
std::pair
不仅仅是简单的
template<typename T, typename U>
struct pair
{
T first;
U second;
};
上面列出了 8 个不同的构造函数参考参数 https://en.cppreference.com/w/cpp/utility/pair/pair,对于实现者来说,甚至更多:每个条件显式构造函数实际上都是two构造函数,一个用于隐式,另一个用于显式。
并非所有这些构造函数都参与重载决策,如果它们参与,到处都会出现歧义。相反,有很多规则来管理每个规则何时执行,并且every上述情况的组合必须由 SFINAE 手动编写和禁用。
多年来,这最终导致了 5 个错误报告单独的构造函数。现在即将成为 6 岁;)
Prologue
The 第一个错误 https://github.com/gcc-mirror/gcc/commit/7b3318c41e6165140e59e026988d1f6e27d01a2a如果类型相同,则短路对参数可转换性的检查。
template<typename T> struct B;
template<typename T> struct A
{
A(A&&) = default;
A(const B<T> &);
};
template<typename T> struct B
{
pair<A<T>, int> a;
B(B&&) = default;
};
显然,如果他们过早检查可转换性,则由于循环依赖以及如何删除移动构造函数,移动构造函数会被删除。B
内仍不完整A
.
nonesuch
然而这更改了 SFINAE 属性 https://github.com/gcc-mirror/gcc/commit/f524d5b34aaac95cb4b2ce7126002cd4fa9d5bae of pair
。作为回应,实施了另一个修复。此实现启用了以前无效的赋值运算符,因此通过更改其签名来手动关闭赋值运算符
struct nonesuch
{
nonesuch() = delete;
~nonesuch() = delete;
nonesuch(nonesuch const&) = delete;
void operator=(nonesuch const&) = delete;
};
// ...
pair& operator=(
conditional_t<conjunction_v<is_copy_assignable<T>,
is_copy_assignable<U>>,
const pair&, const nonesuch&>::type)
Where nonesuch
是一个虚拟类型,本质上使此重载无法调用。或者是吗?
no_braces_nonesuch
不幸的是,即使你永远无法创建一个nonesuch
pair<int, int> p = {}; // succeeds
p = {}; // fails
你仍然可以用大括号初始化它 https://github.com/gcc-mirror/gcc/commit/c1e2889a320a2e45eb60b6bb7c1d3d8fc0068582. Since delete
无法解决重载解析,这是一个硬故障。
解决方法是创建no_braces_nonesuch
struct no_braces_nonesuch : nonesuch
{
explicit no_braces_nonesuch(const no_braces_nonesuch&) = delete;
};
The explicit
关闭参与过载解析。最后,该作业是不可调用的。或者说是……?
__pair_base
v1
不幸的是,有另一种初始化方式 https://github.com/gcc-mirror/gcc/commit/e182158261869320bc6fb1e972fd5a142359965e未知类型
struct anything
{
template<typename T>
operator T() { return {}; }
};
anything a;
pair<int, int> p;
p = a;
作者意识到他们可以通过利用默认生成的特殊成员函数“轻松”解决这个问题:如果您有一个不可分配的基数,则根本无法声明它们
class __pair_base
{
template<typename T, typename U> friend struct pair;
__pair_base() = default;
~__pair_base() = default;
__pair_base(const __pair_base&) = default;
__pair_base& operator=(const __pair_base&) = delete;
};
所有单元测试都通过了,一切看起来都很光明。不知不觉中,一只邪恶虫子的阴影不祥地出现在地平线上。
__pair_base
v2
ABI 崩溃了。
这怎么可能呢?空碱基优化 https://en.cppreference.com/w/cpp/language/ebo他们不是吗?嗯,不。
pair<pair<int, int>, int> p;
不幸的是,空基优化仅适用于基类子对象与相同类型的其他子对象不重叠的情况。在这种情况下,__pair_base
内对的一个与外对的一个重叠。
修复很“简单”,我们将其模板化__pair_base
以确保它们是不同的类型。
结构类型
C++20 来了,它要求这对是结构类型 https://en.cppreference.com/w/cpp/language/template_parameters。这就要求没有私人基地。
template<pair<int, int>>
struct S; // fails
我们的旅程就这样结束了。这让我想起Chandler Carruth 在 cppcon 上的快速调查 https://youtu.be/LJh5QCV4wDg?t=2455:“如果需要,谁可以在一年内构建出 C++ 编译器?”考虑到 C++ 的复杂性,只有当前的编译器编写者认为他们可以。显然,我什至不知道如何实施std::pair
.