它归结为:
#include <memory>
class dummy;
std::unique_ptr<dummy> test;
class dummy {
};
当使用 clang++-14 和 c++2b 编译时:
它简要地给出了:
clang++ ../bugl.cpp -I /usr/lib/llvm-14/include -std=c++2b
In file included from ../bugl.cpp:1:
In file included from /usr/bin/../lib/gcc/x86_64-linux-gnu/12/../../../../include/c++/12/memory:76:
/usr/bin/../lib/gcc/x86_64-linux-gnu/12/../../../../include/c++/12/bits/unique_ptr.h:93:16: error: invalid application of 'sizeof' to an incomplete type 'dummy'
static_assert(sizeof(_Tp)>0,
^~~~~~~~~~~
/usr/bin/../lib/gcc/x86_64-linux-gnu/12/../../../../include/c++/12/bits/unique_ptr.h:396:4: note: in instantiation of member function 'std::default_delete<dummy>::operator()' requested here
get_deleter()(std::move(__ptr));
^
../bugl.cpp:4:24: note: in instantiation of member function 'std::unique_ptr<dummy>::~unique_ptr' requested here
std::unique_ptr<dummy> test;
^
../bugl.cpp:2:7: note: forward declaration of 'dummy'
class dummy;
^
1 error generated.
但是一旦我们删除了dummy
的定义,它就会变得有趣——那么它在任何地方都同样失败。
最初我从 llvm-14 标头中找到它:
#include <memory>
#include <llvm/ADT/APFloat.h>
使用 llvm-14 和 libstdc++-12-dev 以及来自 Ubuntu 22.04.1 LTS 的 clang++-14
编译方式:
clang++ -std=c++2b bugllvm.cpp
使用-std=c++20
它可以很好地编译(以及使用 g++ 的 20 和 2b)。
基本上违规的行是这样的:
class DoubleAPFloat final : public APFloatBase {
// Note: this must be the first data member.
const fltSemantics *Semantics;
std::unique_ptr<APFloat[]> Floats;
如果此时声明了APFloat
但未定义:
class APFloat;
我的问题是错误在哪里 - 在编译器、标准库、标准本身中?
也许是别的什么。
问题在于没有将std::unique_ptr
用作这样的成员。实际问题在第 625 行的operator=
实现中的类定义中进一步出现:
DoubleAPFloat &operator=(DoubleAPFloat &&RHS) {
if (this != &RHS) {
this->~DoubleAPFloat();
new (this) DoubleAPFloat(std::move(RHS));
}
return *this;
}
析构函数调用会导致定义DoubleAPFloat
的析构函数。这需要实例化std::unique_ptr<APFloat[]>
的析构函数。该析构函数的实例化需要实例化std::default_delete<APFloat[]>
的operator()
而这反过来又需要APFloat
才能完成。
函数模板专用化的实例化有两个实例化点。一个紧跟在需要它的命名空间范围声明之后(或在需要它的模板实例化点),第二个在翻译单元的末尾。如果选择其中一个而不是另一个会给程序带来不同的含义,那么程序格式不正确,不需要诊断。
APFloat
稍后在头文件中定义,因此这适用于此处。std::default_delete<APFloat[]>::operator()
的实例化在DoubleAPFloat
的定义之后立即是格式错误的(其中std::unique_ptr
析构函数的实例化有一个实例化点),但它在翻译单元的末尾会形成良好。
因此,这是编译器不必诊断的<llvm/ADT/APFloat.h>
中的错误。
这同样适用于简化的测试用例。test
的定义需要定义析构函数,因为析构函数是销毁变量所必需的。同样,隐式实例化可以紧跟在变量定义之后或翻译单元的末尾。如果最后没有dummy
的定义,两个实例化点的格式都不正确,因此现在需要编译器对其进行诊断。