关于 std::vector 中不完整类型的混乱(或 Clang bug?)

Luk*_*rth 7 c++ llvm clang language-lawyer c++20

C++20 标准在 [vector.overview]/4 中声明

如果分配器满足分配器完整性要求,则在实例化向量时可以使用不完整类型 T。T 应在引用所得向量专业化的任何成员之前完成。

默认分配器std::allocate确实满足allocator completeness requirements. 主要问题是“引用”在这种情况下意味着什么。我感到困惑的代码是以下代码的变体:

#include <vector>

class MyClass;

class MyContainer
{ 
        std::vector<MyClass>  member;
};

class MyClass {};

int main()
{}
Run Code Online (Sandbox Code Playgroud)

上面的代码可以在各种编译器中正常编译。如果我明确默认默认构造函数,它仍然可以编译:

#include <vector>

class MyClass;

class MyContainer
{ 
        MyContainer()  = default;
        std::vector<MyClass>  member;
};

class MyClass {};

int main()
{}
Run Code Online (Sandbox Code Playgroud)

然而,当我将默认构造函数定义为“空”时,会发生一些奇怪的事情。这是代码(位于编译器资源管理器):

#include <vector>

class MyClass;

class MyContainer
{ 
        MyContainer() {};
        std::vector<MyClass>  member;
};

class MyClass {};

int main()
{}
Run Code Online (Sandbox Code Playgroud)

有了这个代码:

  • C++17 模式下的 GCC 12、Clang 14 和 Clang 15 仍然可以编译此文件
  • C++20 模式下的 Clang 15 失败并出现以下错误:
    In file included from <source>:1:
    In file included from /opt/compiler-explorer/gcc-12.2.0/lib/gcc/x86_64-linux-gnu/12.2.0/../../../../include/c++/12.2.0/vector:64:
    /opt/compiler-explorer/gcc-12.2.0/lib/gcc/x86_64-linux-gnu/12.2.0/../../../../include/c++/12.2.0/bits/stl_vector.h:367:35: error: arithmetic on a pointer to an incomplete type 'MyClass'
                                                _M_impl._M_end_of_storage - _M_impl._M_start);
                                                ~~~~~~~~~~~~~~~~~~~~~~~~~ ^
    /opt/compiler-explorer/gcc-12.2.0/lib/gcc/x86_64-linux-gnu/12.2.0/../../../../include/c++/12.2.0/bits/stl_vector.h:526:7: note: in instantiation of member function 'std::_Vector_base<MyClass, std::allocator<MyClass>>::~_Vector_base' requested here
                vector() = default;
                ^
    <source>:7:5: note: in defaulted default constructor for 'std::vector<MyClass>' first required here
            MyContainer() {};
            ^
    <source>:3:7: note: forward declaration of 'MyClass'
    class MyClass;
Run Code Online (Sandbox Code Playgroud)

我的第一直觉,只查看 Clang 15 错误,是“clang 是正确的”。默认构造函数确实(隐式)调用 的默认构造函数std::vector<MyClass>,并且标准规定,只要MyClass不完整,就不能引用成员。

然而,我非常确定这不是答案,因为:

  • 所有其他编译器(见上文)甚至不发出警告
  • Thrift C++ 编译器实际上生成的代码正是执行此操作。

所以,我的问题是:这是 Clang 15 的错误吗?如果是这样,是否(隐式)调用std::vector<MyClass>不被视为“引用”的默认构造函数 [vector.overview]/4?

我确实在LLVM 错误跟踪器中搜索了术语“向量”和“不完整”,但没有找到任何结果,所以如果这是一个已知的错误,我猜它在 的上下文中是未知的std::vector


编辑1:我不认为这是重复的

这是作为两个问题的重复而结束的,在我看来,这是不正确的。差异虽然微妙但相关。这两个问题是:

当与不完整类型一起使用时,std::map::reverse_iterator 不适用于 C++20

  • std::map 规范从未说过(与 std::vector 规范相反,请参阅 [vector.overview]/4)它可以用不完整类型实例化。因此,我的问题(使用 std::vector)中的问题与 std::map 的问题不同
  • 在这个问题中,有问题的 std::map 的对象在不完整类型完成之前被实例化 - 这显然永远不会起作用。在我的问题中,根本没有实例化任何对象。

C++20 对reverse_iterator 的哪些更改破坏了此代码?

  • std::vector<incompleteType>这里,显式引用了的成员(即::reverse_iterator)。这显然没有被[vector.overview]/4涵盖。但是,我在问题中并没有这样做。
  • 同样,不完整类型向量的对象在类型完成之前实例化,这是行不通的,而且我也不想这样做。

Bri*_*ian 3

确实,这里的“引用”并不清楚。我认为这句话的意思可能是,T在你做任何需要最终专业化的任何成员的定义存在的事情之前,它应该是完整的。

在第二个例子中

class MyClass;

class MyContainer
{ 
        MyContainer()  = default;
        std::vector<MyClass>  member;
};

class MyClass {};
Run Code Online (Sandbox Code Playgroud)

std::vector<MyClass>在编译器实际隐式定义封闭类 的默认构造函数之前,不需要 的默认构造函数的定义MyContainer根据[dcl.fct.def.default]/5的最后一句,直到第一次的MyContainer默认构造函数被 odr 使用或需要进行持续评估时,这种情况才会发生:

未定义为已删除的非用户提供的默认函数(即在类中隐式声明或显式默认)在使用 odr ([basic.def.odr]) 或需要常量求值 ([表达式.const])。

您的第一个示例(您没有声明任何默认构造函数)和第二个示例(您在类定义中将其声明为默认构造函数)的处理方式类似:在这两种情况下,构造函数都不是用户提供的,因此它不会急切地定义。在这两个示例中,MyContainer都有一个非用户提供的复制构造函数、移动构造函数、复制赋值运算符、移动赋值运算符和析构函数,它们同样在需要定义之前才定义,因此程序避免“引用”任何成员的std::vector<MyClass>

在第三个示例中,由于构造函数是用户提供的,因此即使从未使用过它,它也会被定义,并且其定义隐式调用 的默认构造函数std::vector<MyClass>后者被“引用”。

当您违反规则时(如在第三个示例中所做的那样),程序将出现未定义的行为。我知道您尝试过的大多数编译器甚至都没有警告您,这似乎非常不友好,但是当您滥用不完整类型时不需要进行诊断是有原因的:模板很难以不导致错误的方式检查完整性。更大的问题(尽管我相信 Clang 正在针对这个问题开展工作)。