A short time ago, we observed that there’s usually no need to wrap a callable in a lambda, and more recently observed that we can apply our principles for reducing C++ template bloat to simplify the function further.
Just to refresh our memories, here is where we left off:
template<typename Lambda>
bool Widget::QueueToWorkerThread(Lambda&& lambda)
{
CreateWorkerThreadIfNeeded();
return m_dispatcherQueue.TryEnqueue(
std::forward<Lambda>(lambda));
}
As I noted earlier, lambdas are sort of the worst-case scenario for templated functions since every lambda is a unique type. Every time you call it, you force the generation of a new function.
But we can lift the lambda out of the body and pass it to a worker function. In this case, the only thing we do with the lambda is used it to construct a DispatcherQueueHandler, so we can construct the DispatcherQueueHandler up front, and use that as the common type.
namespace winrt
{
using namespace winrt::Windows::System;
}
bool Widget::QueueToWorkerThreadWorker(
winrt::DispatcherQueueHandler const& handler)
{
CreateWorkerThreadIfNeeded();
return m_dispatcherQueue.TryEnqueue(handler);
}
template<typename Lambda>
bool Widget::QueueToWorkerThread(Lambda&& lambda)
{
winrt::DispatcherQueueHandler handler(std::forward<Lambda>(lambda));
return QueueToWorkerThreadWorker(handler);
}
our worker function takes the shared type DispatcherQueueHandler, and the main function converts the lambda to the shared type, and then calls the non-templated worker function.
The order of operations changes, but it’s not important whether we construct the DispatcherQueueHandler or late. It’s technically noticeable, because in the event that the CreateWorkerThreadIfNeeded() throws an exception, an rvalue reference to the lambda will be in the moved-from state, but these lambdas are typically created on the fly and discarded, so the caller doesn’t care whether or not it survives the error. (It’s also technically noticeable if the creation of the DispatcherQueueHandler throws an exception, which means that CreateWorkerThreadIfNeeded() is not called at all. Given what we see of the function, that’s not going to be a problem either. All it means that we don’t even bother creating the worker thread.)
But, wait, we can go even further.
We can do the conversion of the lambda to the DispatcherQueueHandler directly in the function parameter!
bool Widget::QueueToWorkerThread(
winrt::DispatcherQueueHandler const& handler)
{
CreateWorkerThreadIfNeeded();
return m_dispatcherQueue.TryEnqueue(handler);
}
When the caller passes a lambda, the conversion constructor from the lambda to DispatcherQueueHandler kicks in at the call site, so it already arrives at the QueueToWorkerThread function in the form of our common type, DispatcherQueueHandler.
Hooray, we were able to de-templatize the function entirely.
0 comments
Be the first to start the discussion.