c++language-lawyerstdvectorforward-declarationincomplete-type

Can one forward-declare a function taking a vector of incomplete type with a default value?


The code snippet below demonstrates a real issue I faced recently in my program:

#include<vector>

class A;

void f( const std::vector<A> & = {} );

There is an incomplete class A, and a function declaration taking a vector of A's with empty default value. And the function is not even called anywhere.

It works fine in GCC, and in Clang 14, but starting from Clang 15 an error appears:

In file included from <source>:1:
/opt/compiler-explorer/clang-15.0.0/bin/../include/c++/v1/vector:540:52: error: arithmetic on a pointer to an incomplete type 'A'
        {return static_cast<size_type>(__end_cap() - this->__begin_);}
                                       ~~~~~~~~~~~ ^
/opt/compiler-explorer/clang-15.0.0/bin/../include/c++/v1/vector:760:56: note: in instantiation of member function 'std::vector<A>::capacity' requested here
      __annotate_contiguous_container(data(), data() + capacity(),
                                                       ^
/opt/compiler-explorer/clang-15.0.0/bin/../include/c++/v1/vector:431:7: note: in instantiation of member function 'std::vector<A>::__annotate_delete' requested here
      __annotate_delete();
      ^
<source>:5:32: note: in instantiation of member function 'std::vector<A>::~vector' requested here
void f( const std::vector<A> & = {} );
                               ^
<source>:3:7: note: forward declaration of 'A'
class A;
      ^

Online demo: https://godbolt.org/z/a8xzshbzP

Are newer versions of Clang correct in rejecting the program?


Solution

  • Yes, Clang is correct to reject the program. Per vector.overview#4:

    An incomplete type T may be used when instantiating vector if the allocator meets the allocator completeness requirements. T shall be complete before any member of the resulting specialization of vector is referenced.

    In the default argument of f you're referencing a constructor of vector<A> before A is complete, so the program is ill-formed.

    Here's a bug report (closed as invalid) showing a similar situation. The comment at the bottom suggests why this may have changed in Clang-15.

    Probably what changed between libc++14 and libc++15 is that the vector move constructor became constexpr, so it's now getting instantiated earlier.