惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Engineering at Meta
Engineering at Meta
D
Docker
IT之家
IT之家
博客园_首页
罗磊的独立博客
V
V2EX
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
美团技术团队
Y
Y Combinator Blog
博客园 - 聂微东
量子位
阮一峰的网络日志
阮一峰的网络日志
GbyAI
GbyAI
Microsoft Security Blog
Microsoft Security Blog
博客园 - Franky
Martin Fowler
Martin Fowler
Jina AI
Jina AI
大猫的无限游戏
大猫的无限游戏
C
Check Point Blog
月光博客
月光博客
G
Google Developers Blog
B
Blog
T
The Blog of Author Tim Ferriss
爱范儿
爱范儿

The Old New Thing

Why didn't Read­Directory­ChangesW provide a way to correlate the two sides of a rename operation? - The Old New Thing How can I remove the Close button from my window caption? - The Old New Thing Why is the x86 undefined instruction called ud2? Why 2? - The Old New Thing What algorithm did Windows XP use to choose your initial user picture? - The Old New Thing A sample use of the winstart.bat file in Windows 95 - The Old New Thing Why don't we allow stacks to be sparse, instead of forcing them to be contiguous? - The Old New Thing What happens if you change a window class's GCL_CB­WND­EXTRA? - The Old New Thing The case of the progress callback that never got called when progress happened - The Old New Thing The perils of binding to value types in XAML - The Old New Thing Microspeak: Funded / unfunded - The Old New Thing AWE does not require PAE, though PAE makes it much more useful - The Old New Thing On forcing all derived classes to implement a specific non-virtual method, part 2 - The Old New Thing On forcing all derived classes to implement a specific non-virtual method, part 1 - The Old New Thing In the product end game, every change carries significant risk, episode 2 - The Old New Thing Why didn't the Windows Entertainment Pack just run the MS-DOS version inside an emulator? - The Old New Thing Comparing the two holograms on the Windows 95 box - The Old New Thing Reducing C++ template bloat by factoring out the type-dependent portions of the function, practical exam - The Old New Thing On wrapping a callable in a lambda that just calls it with the same parameters - The Old New Thing Why did the Microsoft Entertainment Pack for Windows have a special sticker announcing that it also had Tetris? - The Old New Thing How do functions like alloca allocate memory from the stack? - The Old New Thing How do functions like alloca allocate memory from the stack? - The Old New Thing Forcing an ARM64X executable to run as a specific architecture - The Old New Thing A little helper class for managing LPPROC_THREAD_ATTRIBUTE_LISTs - The Old New Thing The comments that go into code versus those that go into the pull request description - The Old New Thing The little-known winstart.bat batch file - The Old New Thing How can I perform a Copy­File&shy in unbuffered mode? - The Old New Thing Creating a fake agile wrapper that is technically agile but is not useful outside its home apartment, part 5 - The Old New Thing Creating a fake agile wrapper that is technically agile but is not useful outside its home apartment, part 4 - The Old New Thing Creating a fake agile wrapper that is technically agile but is not useful outside its home apartment, part 3 - The Old New Thing Creating a fake agile wrapper that is technically agile but is not useful outside its home apartment, part 2 - The Old New Thing
Reducing C++ template bloat by factoring out the type-dep...
Raymond Chen · 2026-08-20 · via The Old New Thing

C++ templates let you reuse code, but it comes at a cost: Each template expansion results in a different function. This is not a big deal for small functions, but the less trivial your function becomes, the larger the cost of the repeated expansions.

This is particularly expensive for functions that accept lambdas because every lambda is a unique type, so each time you invoke the template function with a lambda you get a different template expansion.

Sometimes I see large template functions that have very few type dependencies.

template<typename Table>
void something(Database const& db)
{
    // extensive preparations
    auto statusIndicator = ⟦ calculate status indicator ⟧
    auto primaryTugboat = ⟦ calculate primary tugboat ⟧
    std::vector<Staircase> staircases;

    for (auto&& column : Table::Columns()) {
        ⟦ operate on each column using the stuff we prepared ⟧
        ⟦ maybe add things to the staircases and update the tugboat ⟧
    }

    ⟦ lots more code ⟧
}

In this extreme case, the only type dependency is the Table::Columns(). (A more common source of type dependencies would be method calls on a templated inbound parameter.)

This is a large function, and it will be re-expanded for each Table. Since each table has a different set of columns, and probably a different number of columns, there is no opportunity for COMDAT folding, so the different expansions will all be distinct.

One way to mitigate the explosion is to wrap all the common pieces into a helper object.

struct SomethingState {
    Database const& db;
    Indicator statusIndicator;
    Tugboat primaryTugboat;
    std::vector<Staircase> staircases;

    __declspec(noinline)
    SomethingState(Database const& db) : db(db)
    {
        statusIndicator = ⟦ calulate status indicator ⟧
        primaryTugboat = ⟦ calulate primary tugboat ⟧
    }

    __declspec(noinline)
    void ProcessColumn(Column const& column)
    {
        ⟦ operate on each column using the stuff we prepared ⟧
        ⟦ maybe add things to the staircases and update the tugboat ⟧
    }

    __declspec(noinline)
    void Finish()
    {
        ⟦ lots more code ⟧
    }
};

template<typename Table>
void something(Database const& db)
{
    SomethingState state(db);

    for (auto&& column : Table::Columns()) {
        state.ProcessColumn(column);
    }

    state.Finish();
}

Now, the different expansions of the something function can share the SomethingState constructor and methods, so the unique functions are fairly small.

We mark the SomethingState constructor and methods as “no-inline” to discourage the compiler from inlining them, because inlining them would defeat our factoring. Related: A noinline inline function? What sorcery is this?

Another way to reduce the code explosion problem is to do the factoring the other way: Instead of factoring out the common logic and keeping the type-dependent stuff, we factor out the type-dependent stuff and keep the common logic.

The trick with this approach is finding some common type that all of the expansions share. I’ll assume that the Table::Colums() is a C-style array of Column objects, or a std::vector of Column objects, or a std::array of Column objects, or otherwise something that can produce a std::span of Column objects.

void somethingWorker(Database const& db, std::span<Column> columns)
{
    // extensive preparations
    auto statusIndicator = ⟦ calculate status indicator ⟧
    auto primaryTugboat = ⟦ calculate primary tugboat ⟧
    std::vector<Staircase> staircases;

    for (auto&& column : columns) {
        ⟦ operate on each column using the stuff we prepared ⟧
        ⟦ maybe add things to the staircases and update the tugboat ⟧
    }

    ⟦ lots more code ⟧
}

template<typename Table>
void something(Database const& db)
{
    somethingWorker(db, Table::Columns());
}

We capture the columns ahead of time and then use the captured values to perform the enumeration inside a non-templated worker function. Since the worker function is non-templated, there is no template explosion when it is called by each something<Table>.

One thing to watch out for is that we are changing the order of evaluation, The old code didn’t call Table::Columns() until after the preparations were complete. You can look at the code to confirm, but I suspect that Table::Columns() just returns a reference to some pre-existing source of column information, so it doesn’t matter when you call it. Even if it returned the columns by value (say, by cloning an internal vector), retrieving the columns early does change the point at which that vector is generated, but generating it even if the preparatory steps fail is probably not a problem because (1) generating it has no interesting side effects, (2) the order of evaluation is not important, and (3) the failure case is probably rare, so the extra cost of generating a vector that is not used is inconsequential.

We’ll apply these principles to our previous example and make a surprising discovery that will shock and amaze you.

Category

Topics

Author

Raymond Chen

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.