我有一个模板类,类似于下面的类。 行为并不那么相关,重要的是,它有一个数组作为存储 T 的属性,其大小取决于模板参数 TCapacity。
template <class T, size_t TCapacity>
class MyClass {
public:
explicit MyClass() { container_ = std::array<T, TCapacity>(); }
~MyClass() = default;
MyClass(MyClass const& r) = delete;
MyClass(MyClass&& r) noexcept {
container_ = std::move(r.container_);
}
auto Put(T item) -> void {
index_ = ++index % TCapacity;
container_[index_] = item;
}
[[nodiscard]] auto Get() -> std::optional<T> {
auto result = container_[index_];
return result;
}
private:
std::array<T, TCapacity> container_;
size_t index_ = 0;
};
我使用模板类作为另一个类中的属性,例如:
class AnotherClass{
...
private:
MyClass<SomeType, kSize> mc_;
...
}
据我了解,编译器需要知道
SomeType
有多大才能正确分配内存。
因此,我将SomeType
相应的头文件包含在AnotherClass
的文件中。
IWYU 希望我删除 SomeType
的包含,并用 SomeType
的前向声明替换它。
现在,编译器不知道 SomeType
有多大,并抛出 field has incomplete type
错误。
我不确定IWYU的行为是否对应于这个问题IWYU#1217并且它可以被视为一个错误,或者我是否在做一些根本错误的事情。
如何构建模板类以便 IWYU 不会抱怨?
非静态数据成员的类型必须是完整的,因此
AnotherClass
的定义会导致MyClass<SomeType, kSize>
的隐式实例化。
因为
MyClass<SomeType, kSize>
具有非静态数据成员的类型 std::array<SomeType, kSize>
,所以它的实例化将导致 std::array<SomeType, kSize>
的隐式实例化。
现在,通常如果没有特定的例外,标准库模板的隐式实例化需要模板参数完整。
std::array
没有这样的例外,而且很明显,正如您所说,std::array
只能以实例化需要 SomeType
完成的方式正确实现。
所以是的,
SomeType
必须在定义AnotherClass
之前完成(这是隐式实例化类模板特化的实例化点所在的位置)。
需要注意像这样的模板参数的类型完整性。不仅存在导致编译错误的风险,而且很容易导致未定义的行为。
如果使用不完整类型作为参数实例化标准库模板特化,而这是未明确允许的,则违反了库先决条件,并且程序具有未定义的行为。
举一个这个 UB 的实际表现的例子,当模板使用类型特征时,通常不允许用不完整的类型实例化,很容易发生编译器会缓存类型特征评估的无意义结果对于不完整的类型,稍后将在类型特征应用于同一类型的其他地方使用缓存的结果,导致不正确的类型特征结果和行为,但没有警告或错误。
因此,未经验证就不应该信任IWYU,也不适合尝试错误。