{"id":112629,"date":"2026-08-20T07:00:00","date_gmt":"2026-08-20T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=112629"},"modified":"2026-08-20T21:11:12","modified_gmt":"2026-08-21T04:11:12","slug":"20260820-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20260820-00\/?p=112629\/","title":{"rendered":"Reducing C++ template bloat by factoring out the type-dependent portions of the function"},"content":{"rendered":"<p>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.<\/p>\n<p>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.<\/p>\n<p>Sometimes I see large template functions that have very few type dependencies.<\/p>\n<pre>template&lt;typename Table&gt;\r\nvoid something(Database const&amp; db)\r\n{\r\n    \/\/ extensive preparations\r\n    auto statusIndicator = \u27e6 calculate status indicator \u27e7\r\n    auto primaryTugboat = \u27e6 calculate primary tugboat \u27e7\r\n    std::vector&lt;Staircase&gt; staircases;\r\n\r\n    for (auto&amp;&amp; column : Table::Columns()) {\r\n        \u27e6 operate on each column using the stuff we prepared \u27e7\r\n        \u27e6 maybe add things to the staircases and update the tugboat \u27e7\r\n    }\r\n\r\n    \u27e6 lots more code \u27e7\r\n}\r\n<\/pre>\n<p>In this extreme case, the only type dependency is the <code>Table::Columns()<\/code>. (A more common source of type dependencies would be method calls on a templated inbound parameter.)<\/p>\n<p>This is a large function, and it will be re-expanded for each <code>Table<\/code>. 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.<\/p>\n<p>One way to mitigate the explosion is to wrap all the common pieces into a helper object.<\/p>\n<pre>struct SomethingState {\r\n    Database const&amp; db;\r\n    Indicator statusIndicator;\r\n    Tugboat primaryTugboat;\r\n    std::vector&lt;Staircase&gt; staircases;\r\n\r\n    __declspec(noinline)\r\n    SomethingState(Database const&amp; db) : db(db)\r\n    {\r\n        statusIndicator = \u27e6 calulate status indicator \u27e7\r\n        primaryTugboat = \u27e6 calulate primary tugboat \u27e7\r\n    }\r\n\r\n    __declspec(noinline)\r\n    void ProcessColumn(Column const&amp; column)\r\n    {\r\n        \u27e6 operate on each column using the stuff we prepared \u27e7\r\n        \u27e6 maybe add things to the staircases and update the tugboat \u27e7\r\n    }\r\n\r\n    __declspec(noinline)\r\n    void Finish()\r\n    {\r\n        \u27e6 lots more code \u27e7\r\n    }\r\n};\r\n\r\ntemplate&lt;typename Table&gt;\r\nvoid something(Database const&amp; db)\r\n{\r\n    <span style=\"border: solid 1px currentcolor;\">SomethingState state(db);<\/span>\r\n\r\n    for (auto&amp;&amp; column : Table::Columns()) {\r\n        <span style=\"border: solid 1px currentcolor;\">state.ProcessColumn(column);<\/span>\r\n    }\r\n\r\n    <span style=\"border: solid 1px currentcolor;\">state.Finish();<\/span>\r\n}\r\n<\/pre>\n<p>Now, the different expansions of the <code>something<\/code> function can share the <code>SomethingState<\/code> constructor and methods, so the unique functions are fairly small.<\/p>\n<p>We mark the <code>SomethingState<\/code> constructor and methods as &#8220;no-inline&#8221; to discourage the compiler from inlining them, because inlining them would defeat our factoring. <b>Related<\/b>: <a title=\"A noinline inline function? What sorcery is this?\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20200521-00\/?p=103777\"> A noinline inline function? What sorcery is this<\/a>?<\/p>\n<p>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.<\/p>\n<p>The trick with this approach is finding some common type that all of the expansions share. I&#8217;ll assume that the <code>Table::Colums()<\/code> is a C-style array of <code>Column<\/code> objects, or a <code>std::vector<\/code> of <code>Column<\/code> objects, or a <code>std::array<\/code> of <code>Column<\/code> objects, or otherwise something that can produce a <code>std::span<\/code> of <code>Column<\/code> objects.<\/p>\n<pre>void somethingWorker(Database const&amp; db, <span style=\"border: solid 1px currentcolor;\">std::span&lt;Column&gt; columns<\/span>)\r\n{\r\n    \/\/ extensive preparations\r\n    auto statusIndicator = \u27e6 calculate status indicator \u27e7\r\n    auto primaryTugboat = \u27e6 calculate primary tugboat \u27e7\r\n    std::vector&lt;Staircase&gt; staircases;\r\n\r\n    for (auto&amp;&amp; column : <span style=\"border: solid 1px currentcolor;\">columns<\/span>) {\r\n        \u27e6 operate on each column using the stuff we prepared \u27e7\r\n        \u27e6 maybe add things to the staircases and update the tugboat \u27e7\r\n    }\r\n\r\n    \u27e6 lots more code \u27e7\r\n}\r\n\r\ntemplate&lt;typename Table&gt;\r\nvoid something(Database const&amp; db)\r\n{\r\n    somethingWorker(db, <span style=\"border: solid 1px currentcolor;\">Table::Columns()<\/span>);\r\n}\r\n<\/pre>\n<p>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 <code>something&lt;Table&gt;<\/code>.<\/p>\n<p>One thing to watch out for is that we are changing the order of evaluation, The old code didn&#8217;t call <code>Table::Columns()<\/code> until after the preparations were complete. You can look at the code to confirm, but I suspect that <code>Table::Columns()<\/code> just returns a reference to some pre-existing source of column information, so it doesn&#8217;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.<\/p>\n<p>We&#8217;ll apply these principles to <a title=\"On wrapping a callable in a lambda that just calls it with the same parameters\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20260819-00\/?p=112624\"> our previous example<\/a> and make a surprising discovery that will shock and amaze you.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Looking for consolidation points.<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-112629","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Looking for consolidation points.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112629","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=112629"}],"version-history":[{"count":1,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112629\/revisions"}],"predecessor-version":[{"id":112630,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112629\/revisions\/112630"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=112629"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=112629"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=112629"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}