August 21st, 2026
0 reactions

Reducing C++ template bloat by factoring out the type-dependent portions of the function, practical exam

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 Dispatcher­Queue­Handler, so we can construct the Dispatcher­Queue­Handler 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 Dispatcher­Queue­Handler, 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 Dispatcher­Queue­Handler or late. It’s technically noticeable, because in the event that the Create­Worker­Thread­If­Needed() 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 Dispatcher­Queue­Handler throws an exception, which means that Create­Worker­Thread­If­Needed() 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 Dispatcher­Queue­Handler 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 Dispatcher­Queue­Handler kicks in at the call site, so it already arrives at the Queue­To­Worker­Thread function in the form of our common type, Dispatcher­Queue­Handler.

Hooray, we were able to de-templatize the function entirely.

Topics

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

0 comments