Reputation: 21307
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?
Upvotes: 11
Views: 1050
Reputation: 4249
This is an untested hypothetical solution with possibly many UBs and pitfalls. But I see a case for some PIMPL pattern here:
Given that allegedly every member of std::vector
delegates its element manipulation functionality to the allocator of the vector, if you provide a custom allocator - with all allocator functions declared - compilation succeeds if the implementation of allocator method reside in the same TU that implemented the element type:
class element;
struct pimpl_allocator
: std::allocator<element>{
// redeclare every single method in std::allocator
};
std::vector<element, pimpl_allocator> pimpl_vector;
If the members of pimpl_allocator
are defined in the same CPP file containing full declaration of element
, the program hopefully compiles.
Upvotes: 0
Reputation: 16300
This behavior is new to me, but moving past this apparent limitation, please note that it is still possible to have a default parameter by redeclaring (or defining) the function once the class is complete:
#include<vector>
class A;
void f( const std::vector<A> & );
class A{};
// below this line, it can be in any order
void f( const std::vector<A> & = {} );
... more code ...
void f( const std::vector<A> & a ) {(void)a;}
int main() {
f();
}
https://godbolt.org/z/vMxWhW18v
Upvotes: 7
Reputation: 15918
TL;DR: The declaration is invalid in C++20 and later. This is a consequence of constexpr
vector
.
vector
's destructor is a constexpr member function. ([vector.overview])Therefore, in C++20, the default argument causes the destructor to be instantiated, which demands a complete element type.
Before C++20, the destructor is not instantiated, and thus the declaration is valid.
Upvotes: 2
Reputation: 60268
Yes, Clang is correct to reject the program. Per vector.overview#4:
An incomplete type
T
may be used when instantiatingvector
if the allocator meets the allocator completeness requirements.T
shall be complete before any member of the resulting specialization ofvector
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.
Upvotes: 14