I have this code where I have a class MyElement that holds a MyData<UnderlyingData> that’s constructed during the constructor:

#include <atomic>
#include <iostream>
#include <vector>

struct UnderlyingData {
  int a;
  int b;

  UnderlyingData(int _a, int _b) : a(_a), b(_b) {}
};

template <typename T> class MyData {
public:
  std::atomic<T> data_;

  template <typename... Args>
  MyData(Args &&...args) : data_(T(std::forward<Args>(args)...)) {}
};

class MyElement {
public:
  int c;
  MyData<UnderlyingData> mydata;

  explicit MyElement(int _c) : c(_c), mydata(0, 6) {}
};

int main() {
  std::vector<MyElement> arr;
  arr.emplace_back(5);

  std::cout << arr.at(0).c << " " << arr.at(0).mydata.data_.load().a << " "
            << arr.at(0).mydata.data_.load().b << "\n";

  return 0;
}

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:

  MyElement element(5);
  std::cout << element.c << " " << element.mydata.data_.load().b << "\n";

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 fromvoid std::_Construct(_Tp*, _Args&& ...) [with _Tp = MyElement; _Args = {MyElement}]’
/usr/include/c++/11/bits/stl_uninitialized.h:92:18:   required fromstatic _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 fromvoid 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 toUnderlyingData::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.

  • addie@feddit.uk
    link
    fedilink
    English
    arrow-up
    1
    ·
    1 year ago

    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.

    • pwnna@lemmy.caOP
      link
      fedilink
      English
      arrow-up
      1
      ·
      edit-2
      1 year ago

      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…