As you can see, MyData<UnderlyingData> holds an atomic<UnderlyingData> and the constructor of MyData attempts to perfect forward to the UnderlyingData through the atomic. This code works if I try to construct a MyData<UnderlyingData> variable manually, or a MyElement variable, like this:
However, when I put MyElement into a vector and use either emplace_back or push_back(MyElement(5)), I get this cryptic error:
test.cpp: In instantiation of ‘MyData<T>::MyData(Args&& ...) [with Args = {MyData<UnderlyingData>}; T = UnderlyingData]’:
test.cpp:20:7: required from ‘void std::_Construct(_Tp*, _Args&& ...) [with _Tp = MyElement; _Args = {MyElement}]’
/usr/include/c++/11/bits/stl_uninitialized.h:92:18: required from ‘static _ForwardIterator std::__uninitialized_copy<_TrivialValueTypes>::__uninit_copy(_InputIterator, _InputIterator, _ForwardIterator) [with _InputIterator = std::move_iterator<MyElement*>; _ForwardIterator = MyElement*; bool _TrivialValueTypes = false]’
/usr/include/c++/11/bits/stl_uninitialized.h:151:15: required from ‘_ForwardIterator std::uninitialized_copy(_InputIterator, _InputIterator, _ForwardIterator) [with _InputIterator = std::move_iterator<MyElement*>; _ForwardIterator = MyElement*]’
/usr/include/c++/11/bits/stl_uninitialized.h:333:37: required from ‘_ForwardIterator std::__uninitialized_copy_a(_InputIterator, _InputIterator, _ForwardIterator, std::allocator<_Tp>&) [with _InputIterator = std::move_iterator<MyElement*>; _ForwardIterator = MyElement*; _Tp = MyElement]’
/usr/include/c++/11/bits/stl_uninitialized.h:355:2: required from ‘_ForwardIterator std::__uninitialized_move_if_noexcept_a(_InputIterator, _InputIterator, _ForwardIterator, _Allocator&) [with _InputIterator = MyElement*; _ForwardIterator = MyElement*; _Allocator = std::allocator<MyElement>]’
/usr/include/c++/11/bits/vector.tcc:474:3: required from ‘void std::vector<_Tp, _Alloc>::_M_realloc_insert(std::vector<_Tp, _Alloc>::iterator, _Args&& ...) [with _Args = {int}; _Tp = MyElement; _Alloc = std::allocator<MyElement>; std::vector<_Tp, _Alloc>::iterator = std::vector<MyElement>::iterator]’
/usr/include/c++/11/bits/vector.tcc:121:21: required from ‘std::vector<_Tp, _Alloc>::reference std::vector<_Tp, _Alloc>::emplace_back(_Args&& ...) [with _Args = {int}; _Tp = MyElement; _Alloc = std::allocator<MyElement>; std::vector<_Tp, _Alloc>::reference = MyElement&]’
test.cpp:30:19: required from here
test.cpp:17:34: error: no matching function for call to ‘UnderlyingData::UnderlyingData(MyData<UnderlyingData>)’
17 | MyData(Args &&...args) : data_(T(std::forward<Args>(args)...)) {}
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
test.cpp:9:3: note: candidate: ‘UnderlyingData::UnderlyingData(int, int)’
9 | UnderlyingData(int _a, int _b) : a(_a), b(_b) {}
| ^~~~~~~~~~~~~~
test.cpp:9:3: note: candidate expects 2 arguments, 1 provided
test.cpp:5:8: note: candidate: ‘constexpr UnderlyingData::UnderlyingData(const UnderlyingData&)’
5 | struct UnderlyingData {
| ^~~~~~~~~~~~~~
test.cpp:5:8: note: no known conversion for argument 1 from ‘MyData<UnderlyingData>’ to ‘const UnderlyingData&’
test.cpp:5:8: note: candidate: ‘constexpr UnderlyingData::UnderlyingData(UnderlyingData&&)’
test.cpp:5:8: note: no known conversion for argument 1 from ‘MyData<UnderlyingData>’ to ‘UnderlyingData&&’
I've tried a number of permutations and can't seem to figure it out either. I don't understand why the constructor inferred seems to be UnderlyingData::UnderlyingData(MyData<UnderlyingData>) as I'm just passing two numbers to it... Any advice I can get is much appreciated.
Resolved the issue. Can't use atomic in a vector as it is non copyable and non movable. Error messages are horrible and led me to a wrong place. The few layers of classes and templates along with the error message threw me off completely.
Yeah - I think if your MyData field was std::unique_ptr<std::atomic<T>> data_, then this would work. Introduces the extra expense of a pointer dereference, but taking the lock on an atomic is so expensive CPU-wise that it'll pale in comparison.
One of the benefits of moving to C++20 should be much better error messages for this kind of thing - the error should just tell you which concepts are required and which are missing. Haven't seen any compiler that quite lives up to that, though.
Yeah. The alternative is to move to a deque instead of a vector (as deque doesnt require move or copy) which works for me as I don't need random access, only fast insert and iteration.
I should try this with c++20 and how it does in error messages. Right now I'm on 17...
I've noted that if I removed the atomic, this code also compiles and works without problems. So the problem must have gone wrong there somehow, but only when using emplace_back and push_back(std::move)