eI have a template class, similar to the following class. The behaviour is not that relevant, important is, that it has an array as attribute which stores T and its size depends on the template parameter 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;
};
I use the template class as an attribute in another class, e.g.:
class AnotherClass{
...
private:
MyClass<SomeType, kSize> mc_;
...
}
As far as I understand, the compiler needs to know how big SomeType
is to correctly allocate memory.
Thus, I include the corresponding header file for SomeType
in the file of AnotherClass
.
IWYU wants me to remove the include of SomeType
and replace it by a forward declaration of SomeType
.
Now, the compiler does not know how big SomeType
is, and throws an field has incomplete type
error.
I am not sure whether the behaviour of IWYU corresponds to this issue IWYU#1217 and it can be regarded as a bug, or whether I am doing something fundamentally wrong.
How can I build the template class so that IWYU does not complain?
The proposed solution by 463035818_is_not_an_ai will add unnecessary code to MyClass
.
What I've learned is, that my initial understanding, that the issue raised by IWYU and its proposed solution is invalid.
As a result, I will add // IWYU pragma: keep
to the include that causes the IWYU bug.
This IWYU pragma will silence the warning for this specific include.
The type of a non-static data member must be complete, so the definition of AnotherClass
will cause implicit instantiation of MyClass<SomeType, kSize>
.
Because MyClass<SomeType, kSize>
has the type std::array<SomeType, kSize>
for a non-static data member, its instantiation will cause implicit instantiation of std::array<SomeType, kSize>
.
Now, generally if there is no specific exception, implicit instantiation of a standard library template requires the template arguments to be complete. There is no such exception for std::array
and it is pretty obvious, as you are also saying, that std::array
can only be implemented properly in a way that instantiation requires SomeType
to be complete.
So yes, SomeType
must be complete before the definition of AnotherClass
(which is where the point-of-instantiation of the implicitly instantiated class template specializations will be).
One needs to be careful with type completeness for template arguments like this. There is not only a risk of causing compilation errors, but it can easily cause undefined behavior.
If a standard library template specialization is instantiated with an incomplete type as argument where this is not specifically allowed, then the library preconditions are violated and the program has undefined behavior.
For an example of a practical manifestation of this UB, when type traits are used by the template, which are generally not allowed to be instantiated with incomplete types, it can easily happen that the compiler will cache a nonsense result of the type trait evaluation for the incomplete type and will then later use the cached result in other places where the type trait is applied to the completed type, resulting in incorrect type trait results and behavior, but no warning or error.
So, neither should IWYU be trusted without verification, nor is trial-and-error suitable.