0

Overview

I am implementing a producer-consumer model in C++. I have mulitple consumers, each of which gets its own thread and queue. For easy access, I want to have a mapping of form <ID, consumer instance>, so that I can simply get the consumers thread via lookup when pushing data to it. The compiler throws an error when trying to access the stored instances method.

Code

The consumer class starts a thread with an attached FIFO queue, and waits for items to get pushed in (queue header is available here: https://github.com/cameron314/readerwriterqueue/blob/master/readerwriterqueue.h )

// consumer.h
#include <functional>
#include <thread>
#include "readerwriterqueue.h"
// TYPE HAS TO BE A POINTER
template<typename T>
class Consumer
{
public:
 Consumer(int id, std::function<void(int, T&)> func) :
 m_id(id), m_func(func) {}
 ~Consumer(){
 m_running = false;
 this->pushBack(nullptr);
 m_thread.join();
 }
 // satisfy rule of three; disallow copying as we manage a thread
 Consumer(const Consumer&) = delete;
 Consumer& operator=(const Consumer&) = delete;
 void pushBack(const T& t){
 m_BufferQueue.enqueue(t);
 }
private:
 void work() {
 m_running = true;
 while(m_running || m_BufferQueue.peek())
 {
 T t;
 m_BufferQueue.wait_dequeue(t);
 if (t == nullptr)
 break;
 m_func(m_id, t);
 }
 }
 int m_id;
 std::function<void(int, T&)> m_func;
 moodycamel::BlockingReaderWriterQueue<T> m_BufferQueue{64};
 std::thread m_thread{&Consumer::work, this};
 std::atomic_bool m_running;
};

I want to create multiple instances of this class and place them into an unordered map, so I can push into their queues if necessary. As far as I can tell, emplace helps me to avoid writing a move constructor/assignment operator as it creates and places the object directly in the map.

// main_player.cpp
#include <iostream>
#include <unordered_map>
#include "consumer.h"
void func(int id, int* val)
{
 std::cout << "Thread " << id << " received value " << *val << "\n";
}
int main(int argc, char** argv) {
 std::unordered_map<int, Consumer<int*>> consumers;
 consumers.emplace(std::piecewise_construct, std::forward_as_tuple(0), std::forward_as_tuple(0, func));
 // push into queues here and let the threads do the work
 for(int i = 0; i < 5; i++)
 consumers[0].pushBack(&i)
 return 0;
}

The compiler has a problem with the pushBack into a queue. From the error log, I can infer that he does seem to construct another instance and fails. Can somebody help me out here?

Error Log

In file included from /usr/include/c++/7/functional:54:0,
 from ../consumer.h:4,
 from ../main_player.cpp:31:
/usr/include/c++/7/tuple: In instantiation of ‘std::pair<_T1, _T2>::pair(std::tuple<_Args1 ...>&, std::tuple<_Args2 ...>&, std::_Index_tuple<_Indexes1 ...>, std::_Index_tuple<_Indexes2 ...>) [with _Args1 = {int&&}; long unsigned int ..._Indexes1 = {0}; _Args2 = {}; long unsigned int ..._Indexes2 = {}; _T1 = const int; _T2 = Consumer<int*>]’:
/usr/include/c++/7/tuple:1641:63: required from ‘std::pair<_T1, _T2>::pair(std::piecewise_construct_t, std::tuple<_Args1 ...>, std::tuple<_Args2 ...>) [with _Args1 = {int&&}; _Args2 = {}; _T1 = const int; _T2 = Consumer<int*>]’
/usr/include/c++/7/ext/new_allocator.h:136:4: required from ‘void __gnu_cxx::new_allocator<_Tp>::construct(_Up*, _Args&& ...) [with _Up = std::pair<const int, Consumer<int*> >; _Args = {const std::piecewise_construct_t&, std::tuple<int&&>, std::tuple<>}; _Tp = std::pair<const int, Consumer<int*> >]’
/usr/include/c++/7/bits/alloc_traits.h:475:4: required from ‘static void std::allocator_traits<std::allocator<_CharT> >::construct(std::allocator_traits<std::allocator<_CharT> >::allocator_type&, _Up*, _Args&& ...) [with _Up = std::pair<const int, Consumer<int*> >; _Args = {const std::piecewise_construct_t&, std::tuple<int&&>, std::tuple<>}; _Tp = std::pair<const int, Consumer<int*> >; std::allocator_traits<std::allocator<_CharT> >::allocator_type = std::allocator<std::pair<const int, Consumer<int*> > >]’
/usr/include/c++/7/bits/hashtable_policy.h:2066:37: required from ‘std::__detail::_Hashtable_alloc<_NodeAlloc>::__node_type* std::__detail::_Hashtable_alloc<_NodeAlloc>::_M_allocate_node(_Args&& ...) [with _Args = {const std::piecewise_construct_t&, std::tuple<int&&>, std::tuple<>}; _NodeAlloc = std::allocator<std::__detail::_Hash_node<std::pair<const int, Consumer<int*> >, false> >; std::__detail::_Hashtable_alloc<_NodeAlloc>::__node_type = std::__detail::_Hash_node<std::pair<const int, Consumer<int*> >, false>]’
/usr/include/c++/7/bits/hashtable_policy.h:750:8: required from ‘std::__detail::_Map_base<_Key, _Pair, _Alloc, std::__detail::_Select1st, _Equal, _H1, _H2, _Hash, _RehashPolicy, _Traits, true>::mapped_type& std::__detail::_Map_base<_Key, _Pair, _Alloc, std::__detail::_Select1st, _Equal, _H1, _H2, _Hash, _RehashPolicy, _Traits, true>::operator[](std::__detail::_Map_base<_Key, _Pair, _Alloc, std::__detail::_Select1st, _Equal, _H1, _H2, _Hash, _RehashPolicy, _Traits, true>::key_type&&) [with _Key = int; _Pair = std::pair<const int, Consumer<int*> >; _Alloc = std::allocator<std::pair<const int, Consumer<int*> > >; _Equal = std::equal_to<int>; _H1 = std::hash<int>; _H2 = std::__detail::_Mod_range_hashing; _Hash = std::__detail::_Default_ranged_hash; _RehashPolicy = std::__detail::_Prime_rehash_policy; _Traits = std::__detail::_Hashtable_traits<false, false, true>; std::__detail::_Map_base<_Key, _Pair, _Alloc, std::__detail::_Select1st, _Equal, _H1, _H2, _Hash, _RehashPolicy, _Traits, true>::mapped_type = Consumer<int*>; std::__detail::_Map_base<_Key, _Pair, _Alloc, std::__detail::_Select1st, _Equal, _H1, _H2, _Hash, _RehashPolicy, _Traits, true>::key_type = int]’
/usr/include/c++/7/bits/unordered_map.h:977:20: required from ‘std::unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::mapped_type& std::unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::operator[](std::unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::key_type&&) [with _Key = int; _Tp = Consumer<int*>; _Hash = std::hash<int>; _Pred = std::equal_to<int>; _Alloc = std::allocator<std::pair<const int, Consumer<int*> > >; std::unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::mapped_type = Consumer<int*>; std::unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::key_type = int]’
../main_player.cpp:48:20: required from here
/usr/include/c++/7/tuple:1652:70: error: no matching function for call to ‘Consumer<int*>::Consumer()’
 second(std::forward<_Args2>(std::get<_Indexes2>(__tuple2))...)
 ^
In file included from ../main_player.cpp:31:0:
../consumer.h:13:5: note: candidate: Consumer<T>::Consumer(int, std::function<void(int, T&)>) [with T = int*]
 Consumer(int id, std::function<void(int, T&)> func) :
 ^~~~~~~~
../consumer.h:13:5: note: candidate expects 2 arguments, 0 provided
make: *** [main_player.o] Error 1
subdir.mk:66: recipe for target 'main_player.o' failed
"make -j20 all" terminated with exit code 2. Build might be incomplete.
18:01:03 Build Failed. 3 errors, 0 warnings. (took 739ms)
asked Dec 15, 2021 at 17:21

1 Answer 1

1

You need to add the default constructor in class Consumer as the error says

error: no matching function for call to ‘Consumer<int*>::Consumer()’

You can do so in 2 ways:

Method 1

// consumer.h
#include <functional>
#include <thread>
#include "readerwriterqueue.h"
// TYPE HAS TO BE A POINTER
template<typename T>
class Consumer
{
public:
 Consumer(int id, std::function<void(int, T&)> func) :
 m_id(id), m_func(func) {}
 //other member here
 
 //ADD THE DEFAULT CONSTRUCTOR 
 Consumer() = default;
 
private:
 //...
 int m_id = 0; //USE IN-CLASS INITIALIZER for built in type
 //other members here
};

Method 2

// consumer.h
#include <functional>
#include <thread>
#include "readerwriterqueue.h"
// TYPE HAS TO BE A POINTER
template<typename T>
class Consumer
{
public:
 Consumer(int id, std::function<void(int, T&)> func) :
 m_id(id), m_func(func) {}
 //other member here
 
 //ADD THE DEFAULT CONSTRUCTOR 
 Consumer(): m_id(0)
 {
 }
 
private:
 //...
 int m_id;
 //other members here
};
answered Dec 15, 2021 at 17:26

2 Comments

Thanks, for some reason I always assumed the "normal" constructor was the default constructor. That clears up some confusion :P
@DocDriven You're welcome. The problem was that If you define a class without any constructor, the compiler will synthesize a constructor for you (and that will be a default constructor -- i.e., one that doesn't require any arguments). If, however, you do define a constructor, (even if it does take one or more arguments, like you did in your example) the compiler will not synthesize a constructor for you. So you've to explicitly add the default constructor for your class as i did.

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.