Hello Thrift folks,
I think that the Thrift 0.17.0 C++ generator might generate - even for
simple examples - code that contains undefined behavior, and which
indeed stops compiling with Clang 15 in C++20 mode. Consider this simple
thrift specification:
┌────
│ struct StructA {
│ 11:required list<StructB> myBs
│ }
│
│ struct StructB
│ {
│ 1:required string someString
│ }
└────
I translate this to C++ using this command (and thrift v. 0.17.0):
┌────
│ thrift --gen cpp:no_skeleton -o out ./file.thrift
└────
The interesting parts are in the generated file `file_types.h'. You can
find the full generated file at
<https://gist.github.com/tinloaf/1f7ae57df383d7b87221598e4f7a700d>, but
these are the crucial parts:
┌────
│ class StructA;
│
│ class StructB;
│
│
│ class StructA : … {
│ public:
│
│ …
│ StructA() noexcept {
│ }
│
│ …
│ std::vector<StructB> myBs;
│ …
│ };
│
│ …
│
│ class StructB : … {
│ …
│ };
└────
The important part is that there is a _definition_ (not just a
declaration) of `StructA''s default constructor *before* `StructB' is
complete. This default constructor will implicitly call
`std::vector<StructB>''s default constructor.
The C++20 standard states in [vector.overview]/4 [1]:
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.
So, having a member of type `std::vector<StructB>' (with `StructB' being
an incomplete type at that point) should be fine. However, implicitly
using its default constructor in `StructA()' is probably not. This is
also what Clang 15 tells me when I try to compile this with
`--std=c++20':
┌────
│ In file included from ./file_types.h:12:
│ In file included from
/opt/mentz/packages/x64-linux/include/thrift/Thrift.h:41:
│ In file included from
/usr/bin/../lib/gcc/x86_64-linux-gnu/12/../../../../include/c++/12/vector:64:
│
/usr/bin/../lib/gcc/x86_64-linux-gnu/12/../../../../include/c++/12/bits/stl_vector.h:367:35:
error: arithmetic on a pointer to an incomplete type 'StructB'
│ _M_impl._M_end_of_storage -
_M_impl._M_start);
│ ~~~~~~~~~~~~~~~~~~~~~~~~~ ^
│
/usr/bin/../lib/gcc/x86_64-linux-gnu/12/../../../../include/c++/12/bits/stl_vector.h:526:7:
note: in instantiation of member function 'std::_Vector_base<StructB,
std::allocator<StructB>>::~_Vector_base' requested here
│ vector() = default;
│ ^
│ ./file_types.h:34:3: note: in defaulted default constructor for
'std::vector<StructB>' first required here
│ StructA() noexcept {
│ ^
│ ./file_types.h:26:7: note: forward declaration of 'StructB'
│ class StructB;
└────
Here is a (closed as invalid) LLVM issue that is more or less the same
problem (it's the move constructor being used, not the default
constructor): <https://github.com/llvm/llvm-project/issues/57700>
I think this should be fixed by moving all member function definitions
from `*_types.h' into `*_types.cpp' (all types should be complete in the
`.cpp' files). It is not sufficient to just move the default
constructor, in my generated file I see at least `StructA::operator=='
and `StructA::operator!=' which also use members of
`std::vector<StructB>'.
I will gladly open an issue for this, but I think I would need somebody
to create a Jira account for me.
All the best, Lukas
[1]: <https://eel.is/c++draft/vector#overview-4>
--
________________________________________________________
Lukas Barth
MENTZ GmbH, Grillparzerstr. 18, 81675 Munich
P:+49 (0)89 4 18 68-258, F:+49 (0)89 4 18 68-160
E: [email protected], www.mentz.net
Registered Office: Grillparzerstraße 18, 81675 Munich, Germany
President: Christoph Mentz, District Court Munich, HRB 91898
________________________________________________________