I am writing a class to implement and signal-slot mechanism. The signal can emit a series of events that are all derived from a base struct called "base_event". Below is how I defined the base_event and an example of a derived struct:
struct base_event
{
std::string _id = "base_event";
};
struct select_next_event : public base_event
{
select_next_event(uint32_t tr_num) : base_event(), _tr_num(tr_num)
{
base_event::_id = "select_next_event";
};
uint32_t _tr_num;
};
Then my emit function signature is:
template<typename... Args>
void emit(Args... args)
{
..... All the logic .....
}
Then when I want to emit an event, I write something like:
slot.emit(select_next_event{3});
Up to there, everything is working fine.
My issue is that I would like to add an asynchronous (non-blocking) emit function to my library. To that goal, I write a second function (I am using c 20 so I can do perfect forwarding):
void emit_async(Args... args)
{
auto send_async = std::async(std::launch::async,
[this, ... args = std::forward<Args>(args)](){ emit(std::forward<Args>(args)...); });
}
The problem arises if I write:
slot.emit_async(select_next_event{3});
Then when I read the event in my slot function, the value of _tr_num is not forwarded (always equal to 0)
However, if I write :
auto send_async = std::async(std::launch::async,
[this](){slot.emit(select_next_event{3});});
Then the value of _tr_num is correctly forwarded to the slot function.
I do not see where is my error?
Pi-r
[EDIT]
As requested, and sorry for my lack of clarity, please find bellow a minimal example that demonstrates my problem:
#include <iostream>
#include <utility>
#include <future>
#include <vector>
struct base_event
{
std::string _id = "base_event";
};
struct select_next_event : public base_event
{
select_next_event(uint32_t tr_num) : base_event(), _tr_num(tr_num)
{
base_event::_id = "select_next_event";
};
uint32_t _tr_num;
};
template<typename... Args>
void emit(Args... args)
{
int i = 0;
([&] (auto & arg)
{
i;
std::cout << "input " << i << " = " << arg._id.c_str() << " " << arg._tr_num << std::endl;
} (args), ...);
}
template<typename... Args>
void emit_async(Args... args)
{
auto send_async = std::async(std::launch::async,
[... args = std::forward<Args>(args)](){ emit(std::forward<Args>(args)...); });
}
int main()
{
emit(select_next_event{3});
//emit_async(select_next_event{3}); // if added, it produces a compilation eror
}
I do compile the code with:
g -std=c 20 -O2 -Wall -pedantic -pthread main.cpp && ./a.out
I think this example presents the problem I have as if I remove the comment for the async, I do have a compilation error:
main.cpp:38:82: error: binding reference of type ‘std::remove_reference<select_next_event>::type&’ {aka ‘select_next_event&’} to ‘const select_next_event’ discards qualifiers
I do hope that now I have created a clearer explanation of my issue! If anything is missing/misleading, please let me know!
CodePudding user response:
There are two problems here.
First, emit_async(Args... args)
is pass by value, so Args...
is always a value type, you need to add forwarding reference to it.
Second and more important, you construct args
with init-capture [... args = std::forward<Args>(args)]
, but since lambda's operator()
is implicitly const
, you cannot forward args
in the function body since args
are also const
-qualified, instead you need to add mutable
keyword for lambda
template<typename... Args>
void emit_async(Args&&... args) {
auto send_async = std::async(
std::launch::async,
[...args = std::forward<Args>(args)] mutable
{ emit(std::forward<Args>(args)...); });
}