c++templatesforward-declarationiwyu

Include What You Use wants a forward declaration of a type stored in an array in a template class which results in a field has incomplete type error


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.


Solution

  • 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.