-
Notifications
You must be signed in to change notification settings - Fork 46
Expand file tree
/
Copy pathfeed.xml
More file actions
2790 lines (2155 loc) · 395 KB
/
Copy pathfeed.xml
File metadata and controls
2790 lines (2155 loc) · 395 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.8.7">Jekyll</generator><link href="http://cppalliance.org/feed.xml" rel="self" type="application/atom+xml" /><link href="http://cppalliance.org/" rel="alternate" type="text/html" /><updated>2026-05-12T01:20:16+00:00</updated><id>http://cppalliance.org/feed.xml</id><title type="html">The C++ Alliance</title><subtitle>The C++ Alliance is dedicated to helping the C++ programming language evolve. We see it developing as an ecosystem of open source libraries and as a growing community of those who contribute to those libraries..</subtitle><entry><title type="html">MrDocs in the Wild</title><link href="http://cppalliance.org/alan/2026/04/24/Alan.html" rel="alternate" type="text/html" title="MrDocs in the Wild" /><published>2026-04-24T00:00:00+00:00</published><updated>2026-04-24T00:00:00+00:00</updated><id>http://cppalliance.org/alan/2026/04/24/Alan</id><content type="html" xml:base="http://cppalliance.org/alan/2026/04/24/Alan.html"><p>The questions changed. For a long time, people asked about <a href="https://www.mrdocs.com">MrDocs</a> in the abstract: what formats will it support, how will it handle templates, when will it be ready. Then, gradually, the questions became specific. <a href="https://github.com/jll63">Jean-Louis Leroy</a>, the author of <strong><a href="https://github.com/boostorg/openmethod">Boost.OpenMethod</a></strong>, became one of our most active sources of feedback. His library exercises corners of C++ that most projects never touch, which means MrDocs gets tested in ways we would not have anticipated. He wanted to know why his template specializations were not sorted correctly. He wanted <strong>macro support</strong> because Boost libraries rely heavily on macros. He hit a <strong>crash</strong> when his doc comments contained HTML tables. These are not theoretical questions about a tool that might exist someday. These are questions from someone who already generated documentation with MrDocs and needs it to work better.</p>
<p>In our <a href="/alan/2025/10/28/Alan.html">previous post</a>, we described MrDocs transitioning from prototype to product. This post is about what happened when MrDocs went into the wild.</p>
<!-- prettier-ignore -->
<ul id="markdown-toc">
<li><a href="#real-projects-real-problems" id="markdown-toc-real-projects-real-problems">Real Projects, Real Problems</a> <ul>
<li><a href="#the-demo-page" id="markdown-toc-the-demo-page">The Demo Page</a></li>
<li><a href="#breadcrumbs-without-a-navigation-file" id="markdown-toc-breadcrumbs-without-a-navigation-file">Breadcrumbs Without a Navigation File</a></li>
<li><a href="#coordinating-two-independent-extensions" id="markdown-toc-coordinating-two-independent-extensions">Coordinating Two Independent Extensions</a></li>
<li><a href="#edge-cases-in-the-wild" id="markdown-toc-edge-cases-in-the-wild">Edge Cases in the Wild</a></li>
<li><a href="#rendering-and-output" id="markdown-toc-rendering-and-output">Rendering and Output</a></li>
<li><a href="#under-the-hood" id="markdown-toc-under-the-hood">Under the Hood</a></li>
<li><a href="#the-mrdocs-website" id="markdown-toc-the-mrdocs-website">The MrDocs Website</a></li>
</ul>
</li>
<li><a href="#exploring-the-unknowns" id="markdown-toc-exploring-the-unknowns">Exploring the Unknowns</a> <ul>
<li><a href="#reflection-replacing-boilerplate-with-introspection" id="markdown-toc-reflection-replacing-boilerplate-with-introspection">Reflection: Replacing Boilerplate with Introspection</a></li>
<li><a href="#first-steps-toward-extensions" id="markdown-toc-first-steps-toward-extensions">First Steps Toward Extensions</a></li>
<li><a href="#why-we-discarded-mrdocs-as-compiler" id="markdown-toc-why-we-discarded-mrdocs-as-compiler">Why We Discarded MrDocs-as-Compiler</a></li>
</ul>
</li>
<li><a href="#contributor-experience" id="markdown-toc-contributor-experience">Contributor Experience</a> <ul>
<li><a href="#automating-pr-reviews" id="markdown-toc-automating-pr-reviews">Automating PR Reviews</a></li>
<li><a href="#ci-infrastructure" id="markdown-toc-ci-infrastructure">CI Infrastructure</a></li>
<li><a href="#test-infrastructure" id="markdown-toc-test-infrastructure">Test Infrastructure</a></li>
</ul>
</li>
<li><a href="#acknowledgments-and-reflections" id="markdown-toc-acknowledgments-and-reflections">Acknowledgments and Reflections</a></li>
</ul>
<h1 id="real-projects-real-problems">Real Projects, Real Problems</h1>
<div class="mermaid">
%%{init: {"theme": "base", "themeVariables": {"primaryColor": "#e4eee8", "primaryBorderColor": "#affbd6", "primaryTextColor": "#000000", "lineColor": "#baf9d9", "secondaryColor": "#f0eae4", "tertiaryColor": "#ebeaf4", "fontSize": "14px"}}}%%
mindmap
root((Feedback))
First impressions
Unstyled demos
Custom stylesheets
Navigation
Orphaned pages
Breadcrumbs
AST edge cases
Parameter packs
Friend targets
Detail namespaces
Rendering
Description ordering
Code blocks
Anchor links
Runtime
JS engine switch
Compiler fallback
</div>
<h2 id="the-demo-page">The Demo Page</h2>
<p>Right after the <a href="/alan/2025/10/28/Alan.html">previous post</a>, where we announced the MVP and encouraged people to try MrDocs, we noticed the <strong><a href="https://www.mrdocs.com/demos">demos page</a></strong> was not doing us any favors. Someone shared MrDocs on a developer community and the website started getting traffic. The landing page looked polished, but visitors clicked through to the demos and saw raw, unstyled HTML: no fonts, no spacing, no colors. The HTML generator produced correct semantic markup, and that is technically the point: users are supposed to customize the output with their own stylesheets. But on the demos page, there was no stylesheet at all, and the result looked broken rather than customizable.</p>
<p>The <strong><a href="https://github.com/cppalliance/mrdocs/pull/1122">custom stylesheet system</a></strong> added five configuration options (<code>stylesheets</code>, <code>linkcss</code>, <code>copycss</code>, <code>no-default-styles</code>, <code>stylesdir</code>) so projects can match their own branding. A bundled default CSS now ships with MrDocs, and it was <a href="https://github.com/cppalliance/mrdocs/pull/1101">refined</a> to remove gradients in favor of solid, readable backgrounds.</p>
<details>
<summary>Stylesheet commits</summary>
<ul>
<li><a href="https://github.com/cppalliance/mrdocs/commit/5fe30c1">5fe30c1</a> feat: custom stylesheets</li>
<li><a href="https://github.com/cppalliance/mrdocs/commit/33d985c">33d985c</a> chore: version is 0.8.0</li>
</ul>
</details>
<h2 id="breadcrumbs-without-a-navigation-file">Breadcrumbs Without a Navigation File</h2>
<p>MrDocs generates <strong>thousands of reference pages</strong>, one per C++ symbol. We maintain an <a href="https://antora.org/">Antora</a> extension, the <strong><a href="https://github.com/cppalliance/antora-cpp-reference-extension">antora-cpp-reference-extension</a></strong>, that integrates these pages into Antora-based documentation sites. But the generated pages end up orphaned from the navigation tree. Users found the navigation confusing: clicking on “boost” in the breadcrumb did not go where expected, and reference pages had no trail showing where they belonged in the hierarchy.</p>
<p>The obvious fix would be to list every page in Antora’s <code>nav.adoc</code>, but maintaining a navigation file with thousands of entries that changes every time a symbol is added or removed is not practical. Worse, Antora renders the navigation file in the <strong>sidebar</strong>, so listing every reference page would flood the UI with thousands of entries. We discussed the problem extensively with the <a href="https://antora.org/">Antora</a> maintainer on the <a href="https://antora.zulipchat.com/">Antora community chat</a>. His position was clear: Antora was designed so that pages must be in the navigation file. Programmatic editing of navigation is not supported.</p>
<p>That was not acceptable for us. We needed breadcrumbs that work for thousands of generated pages without polluting the sidebar or requiring a hand-maintained navigation file. The Antora author’s position was reasonable from his perspective (Antora is a general-purpose documentation tool, not a reference generator), but our use case was fundamentally different from what Antora was designed for.</p>
<p>The <strong><a href="https://github.com/cppalliance/antora-cpp-reference-extension">antora-cpp-reference-extension</a></strong> now <a href="https://github.com/cppalliance/antora-cpp-reference-extension/commit/ae95eb2">builds breadcrumbs independently</a> from the navigation file. MrDocs generates reference pages in a directory structure that mirrors the C++ namespace hierarchy (<code>boost/urls/segments_view.adoc</code> lives inside <code>boost/urls/</code>). The extension uses this structure to reconstruct the breadcrumb trail: each directory maps to a namespace, and the page title (which is the symbol name) becomes the last breadcrumb entry. The result reads naturally: <strong>Reference &gt; boost &gt; urls &gt; segments_view</strong>.</p>
<p>Zero changes to the nav file. The sidebar stays clean. Breadcrumbs appear automatically and update when symbols are added or removed.</p>
<details>
<summary>Breadcrumb and reference extension commits</summary>
<p><strong><a href="https://github.com/cppalliance/antora-cpp-reference-extension">antora-cpp-reference-extension</a></strong></p>
<ul>
<li><a href="https://github.com/cppalliance/antora-cpp-reference-extension/commit/ae95eb2">ae95eb2</a> feat: synthesize reference breadcrumbs without nav files</li>
<li><a href="https://github.com/cppalliance/antora-cpp-reference-extension/commit/10a4019">10a4019</a> feat: add auto base URL detection</li>
<li><a href="https://github.com/cppalliance/antora-cpp-reference-extension/commit/6a6c08b">6a6c08b</a> docs: auto-base-url option</li>
<li><a href="https://github.com/cppalliance/antora-cpp-reference-extension/commit/4f7c79f">4f7c79f</a> refactor: enhance release asset validation</li>
</ul>
</details>
<h2 id="coordinating-two-independent-extensions">Coordinating Two Independent Extensions</h2>
<p>The <strong><a href="https://github.com/cppalliance/antora-cpp-reference-extension">antora-cpp-reference-extension</a></strong> generates reference pages and breadcrumbs. The <strong><a href="https://github.com/cppalliance/antora-cpp-tagfiles-extension">antora-cpp-tagfiles-extension</a></strong> resolves cross-library symbol links (so a reference to <code>boost::system::error_code</code> in Boost.URL’s docs links to the correct page in Boost.System’s docs). These are <strong>two independent Antora extensions</strong> running as separate jobs.</p>
<p>The problem was that the reference extension generates <strong>tagfiles</strong> as a side effect of producing reference pages, and the tagfiles extension needs the <strong>most recent version</strong> of those tagfiles to resolve links correctly. MrDocs changes the tagfiles every time the corpus changes. Manually keeping them in sync was not sustainable: committing tagfiles to the repository meant they were always stale by the time the next build ran.</p>
<p>We made the extensions <a href="https://github.com/cppalliance/antora-cpp-reference-extension/commit/6e8ffcb">coordinate directly</a>. The reference extension now hands its tagfile to the tagfiles extension at build time, so the links always reflect the current state of the documentation. The reference extension also gained <strong><a href="https://github.com/cppalliance/antora-cpp-reference-extension/commit/10a4019">auto base URL detection</a></strong>, removing the need for manual path configuration when switching between development and production builds.</p>
<details>
<summary>Extension coordination commits</summary>
<p><strong><a href="https://github.com/cppalliance/antora-cpp-reference-extension">antora-cpp-reference-extension</a></strong></p>
<ul>
<li><a href="https://github.com/cppalliance/antora-cpp-reference-extension/commit/6e8ffcb">6e8ffcb</a> feat: antora-cpp-tagfiles-extension coordination</li>
<li><a href="https://github.com/cppalliance/antora-cpp-reference-extension/commit/8f12576">8f12576</a> chore: version is 0.1.0</li>
</ul>
<p><strong><a href="https://github.com/cppalliance/antora-cpp-tagfiles-extension">antora-cpp-tagfiles-extension</a></strong></p>
<ul>
<li><a href="https://github.com/cppalliance/antora-cpp-tagfiles-extension/commit/98eba40">98eba40</a> feat: antora-cpp-reference-extension coordination</li>
<li><a href="https://github.com/cppalliance/antora-cpp-tagfiles-extension/commit/5a1723c">5a1723c</a> feat: add global log level control for missing symbols</li>
<li><a href="https://github.com/cppalliance/antora-cpp-tagfiles-extension/commit/453f01b">453f01b</a> chore: version is 0.1.0</li>
</ul>
</details>
<h2 id="edge-cases-in-the-wild">Edge Cases in the Wild</h2>
<p>As more libraries adopted MrDocs, edge cases in C++ symbol extraction surfaced. <a href="https://github.com/boostorg/beast">Boost.Beast</a> exposed a <strong>duplicate ellipsis</strong> in parameter pack rendering (<a href="https://github.com/cppalliance/mrdocs/issues/1108">#1108</a>, <a href="https://github.com/cppalliance/mrdocs/issues/1129">#1129</a>):</p>
<p><strong>Before:</strong> <code>T&amp; emplace(Args...&amp;&amp;... args)</code></p>
<p><strong>After:</strong> <code>T&amp; emplace(Args&amp;&amp;... args)</code></p>
<p><a href="https://github.com/boostorg/openmethod">Boost.OpenMethod</a> revealed that <strong>friend targets</strong> were not resolving correctly. <a href="https://github.com/cppalliance/buffers">Boost.Buffers</a> uncovered a problem with <strong>detail namespaces</strong>: when a class inherits from a base in a hidden namespace, the inherited members appeared in the documentation but their doc comments were lost (<a href="https://github.com/cppalliance/mrdocs/issues/1107">#1107</a>). We <a href="https://github.com/cppalliance/mrdocs/pull/1109">fixed this</a> so derived classes inherit documentation from hidden bases.</p>
<p><strong>Unnamed structs</strong> also sparked an extended design discussion. When C++ code declares <code>constexpr struct {} f{};</code>, MrDocs needs a <strong>stable, unique name</strong> for hyperlinks. The team established a collaborative design process using shared documents, with <a href="https://github.com/pdimov">Peter Dimov</a> contributing an insight about C compatibility (<code>typedef struct {} T;</code> makes the struct named in C++).</p>
<details>
<summary>AST and metadata commits</summary>
<ul>
<li><a href="https://github.com/cppalliance/mrdocs/commit/c85be75">c85be75</a> fix: remove duplicate ellipsis in parameter pack expansion</li>
<li><a href="https://github.com/cppalliance/mrdocs/commit/c3dbded">c3dbded</a> fix(ast): prevent TU parent from including unmatched globals</li>
<li><a href="https://github.com/cppalliance/mrdocs/commit/76b7b43">76b7b43</a> fix(ast): canonicalize friend targets</li>
<li><a href="https://github.com/cppalliance/mrdocs/commit/05f5852">05f5852</a> fix(metadata): copy impl-defined base docs</li>
<li><a href="https://github.com/cppalliance/mrdocs/commit/35cf1f6">35cf1f6</a> fix: UsingSymbol is SymbolParent</li>
<li><a href="https://github.com/cppalliance/mrdocs/commit/c406d57">c406d57</a> fix: preserve extraction mode when copying members from derived classes</li>
<li><a href="https://github.com/cppalliance/mrdocs/commit/4e7ef04">4e7ef04</a> fix: prevent infinite recursion when extracting non-regular base class</li>
<li><a href="https://github.com/cppalliance/mrdocs/commit/0a69301">0a69301</a> fix: extract and fix some special member function helpers</li>
</ul>
</details>
<h2 id="rendering-and-output">Rendering and Output</h2>
<p>Users noticed that the <strong>manual description</strong> of a symbol was buried below long member tables (<a href="https://github.com/cppalliance/mrdocs/issues/1105">#1105</a>). On a class with many members, you had to scroll past the entire member listing before finding the author’s explanation of what the class does. We moved the description to appear <strong>immediately after the synopsis</strong>, matching what <a href="https://en.cppreference.com/">cppreference</a> does.</p>
<p>Other rendering issues included HTML code blocks not wrapped in <code>&lt;pre&gt;</code> tags, <strong>anchor links</strong> appearing when the wrapper element was missing, and the Handlebars template engine accumulating <strong>special name re-mappings</strong> that conflated different symbols.</p>
<details>
<summary>Rendering and output commits</summary>
<ul>
<li><a href="https://github.com/cppalliance/mrdocs/commit/d90eae6">d90eae6</a> fix: hide anchor links when wrapper is not included</li>
<li><a href="https://github.com/cppalliance/mrdocs/commit/92491de">92491de</a> fix: manual description comes before member lists</li>
<li><a href="https://github.com/cppalliance/mrdocs/commit/58bf524">58bf524</a> fix: remove all special name re-mappings for Handlebars</li>
<li><a href="https://github.com/cppalliance/mrdocs/commit/2a75692">2a75692</a> fix: HTML code blocks not wrapped in pre tags</li>
<li><a href="https://github.com/cppalliance/mrdocs/commit/1ebff32">1ebff32</a> fix: bottomUpTraverse() skips ListBlock items</li>
<li><a href="https://github.com/cppalliance/mrdocs/commit/7b118b1">7b118b1</a> fix: missing @return command in doc comment</li>
</ul>
</details>
<h2 id="under-the-hood">Under the Hood</h2>
<p>We fixed a <strong>compiler fallback</strong> issue where MrDocs failed when the compilation database referenced a compiler that was not available on the current machine, and corrected <strong>sanitizer flag propagation</strong> so that UBSan and TSan do not unnecessarily propagate to dependency builds.</p>
<details>
<summary>Build and toolchain commits</summary>
<ul>
<li><a href="https://github.com/cppalliance/mrdocs/commit/235f5c8">235f5c8</a> fix: fall back to system compilers when database compiler is unavailable</li>
<li><a href="https://github.com/cppalliance/mrdocs/commit/f320581">f320581</a> fix: don’t pass sanitizer to dependency builds for UBSan/TSan</li>
</ul>
</details>
<h2 id="the-mrdocs-website">The MrDocs Website</h2>
<p>While we were fixing the generated output, <strong><a href="https://github.com/rbbeeston">Robert Beeston</a></strong> and <strong><a href="https://github.com/julioest">Julio Estrada</a></strong> were redesigning the <a href="https://www.mrdocs.com">MrDocs website</a>. Robert led the design direction, working with a team to develop a visual identity that balances a distinctive retro aesthetic with modern readability, including a dark theme. Julio handled the implementation: <a href="https://github.com/cppalliance/mrdocs/pull/1032">mobile-responsive layout</a>, <a href="https://github.com/cppalliance/mrdocs/pull/1050">UI styling improvements</a>, <a href="https://github.com/cppalliance/mrdocs/commit/86ce271">cleaner backgrounds and styles</a>, <a href="https://github.com/cppalliance/mrdocs/pull/1075">Open Graph and Twitter meta tags</a> for social sharing, and a <a href="https://github.com/cppalliance/mrdocs/pull/1033">close button for the docs navigation</a> on smaller screens.</p>
<p>For a documentation tool, the website is the first thing potential users see. Having a polished, memorable landing page matters more than it might for other kinds of projects.</p>
<h1 id="exploring-the-unknowns">Exploring the Unknowns</h1>
<p>The team made a deliberate choice: instead of following a <strong>traditional feature roadmap</strong>, we would focus on <strong>areas of uncertainty</strong> (<a href="https://github.com/cppalliance/mrdocs/issues/1113">#1113</a>). These were <strong>open questions</strong> that blocked multiple design decisions at once:</p>
<ul>
<li><strong>MrDocs-as-compiler</strong> (<a href="https://github.com/cppalliance/mrdocs/issues/1073">#1073</a>): should MrDocs emit “object” files for later “linking,” like a compiler?</li>
<li><strong>Scripting extensions</strong> (<a href="https://github.com/cppalliance/mrdocs/issues/1128">#1128</a>, <a href="https://github.com/cppalliance/mrdocs/issues/881">#881</a>): how should users extend and transform documentation output?</li>
<li><strong>Plugins</strong> (<a href="https://github.com/cppalliance/mrdocs/issues/58">#58</a>, <a href="https://github.com/cppalliance/mrdocs/issues/1044">#1044</a>): how should third-party code register new generators?</li>
<li><strong>JSON-only MrDocs</strong>: should we add a JSON output format alongside (or replacing) the existing XML structured output?</li>
<li><strong>Reflection</strong> (<a href="https://github.com/cppalliance/mrdocs/issues/1114">#1114</a>): how do we reduce the maintenance burden of the growing metadata model?</li>
<li><strong>Cross-linking</strong> (<a href="https://github.com/cppalliance/mrdocs/issues/1072">#1072</a>): how do we reference symbols in other libraries?</li>
</ul>
<p>The motivation was practical. Each Boost library that adopted MrDocs had its own needs that could not be met by the core tool alone. <a href="https://github.com/boostorg/url">Boost.URL</a> has <code>implementation_defined</code> namespaces with internal code that should be hidden or transformed in the documentation. <a href="https://github.com/cppalliance/capy">Boost.Capy</a> has detail types that should be presented as user-facing types. Coroutines are represented as types in the AST but should be documented as functions. We want MrDocs to be smart enough, with project-specific extensions, that library authors do not have to do workarounds in the source code just to get the documentation right.</p>
<p>Rather than hard-coding solutions for each library, the unknowns framework asked: what general mechanisms would let every library solve its own documentation problems?</p>
<div class="mermaid">
%%{init: {"theme": "base", "themeVariables": {"primaryColor": "#f7f9ff", "primaryBorderColor": "#9aa7e8", "primaryTextColor": "#1f2a44", "lineColor": "#b4bef2", "secondaryColor": "#fbf8ff", "tertiaryColor": "#ffffff", "fontSize": "14px"}}}%%
mindmap
root((Unknowns))
Scripting extensions
JS helpers
Lua
Plugins
Generator API
DLL loading
Reflection
Boost.Describe
MrDocs.Describe
Cross-linking
Tagfiles
Antora coordination
JSON-only MrDocs
MrDocs-as-compiler
</div>
<h2 id="reflection-replacing-boilerplate-with-introspection">Reflection: Replacing Boilerplate with Introspection</h2>
<p>MrDocs models many kinds of C++ symbols: functions, classes, namespaces, enums, typedefs, concepts, and more. Each symbol type has metadata, and every piece of code that touches that metadata had to <strong>enumerate all fields by hand</strong>. Adding a single field to a symbol type meant updating it in:</p>
<ul>
<li><strong>Schema files</strong> that describe the metadata format</li>
<li><strong>Generators</strong> (HTML, AsciiDoc, XML) that produce the output</li>
<li><strong>Templates</strong> that render individual pages</li>
<li><strong>Operators</strong> like comparison functions, merge functions (e.g., merging symbols from different translation units when only one is documented), and equality checks</li>
<li><strong>Documentation</strong> describing the metadata</li>
<li><strong>The code itself</strong> that populates and transforms the metadata</li>
</ul>
<p>That is roughly <strong>ten to fifteen places</strong> per field, and missing one caused CI failures that blocked everyone. This was one of the <strong>unknowns</strong> we identified: how to reduce the maintenance burden as the data model grows. Worse, downstream users who had their own templates and extensions also had to learn about the new fields and update everything accordingly.</p>
<p><strong><a href="https://github.com/gennaroprota">Gennaro Prota</a></strong>, with his strong background in generic programming and metaprogramming, took ownership of the reflection problem. The work progressed through several stages:</p>
<ol>
<li><a href="https://github.com/cppalliance/mrdocs/pull/1130">Integrate Boost.Describe</a> into the metadata system, replacing hand-written serialization functions</li>
<li><a href="https://github.com/cppalliance/mrdocs/pull/1153">Add <code>$meta.type</code> and <code>$meta.bases</code></a> to all DOM objects so templates can introspect the corpus</li>
<li><a href="https://github.com/cppalliance/mrdocs/pull/1151">Replace the XML generator</a> with a reflection-based one (no more hand-maintained XML output)</li>
<li>Build a <a href="https://github.com/cppalliance/mrdocs/pull/1171">custom reflection system (MrDocs.Describe)</a> tailored to our needs</li>
<li><a href="https://github.com/cppalliance/mrdocs/pull/1177">Replace per-type operators</a> with a single generic template</li>
</ol>
<p>The result <strong>eliminated the second step entirely</strong>: adding a new field to a symbol type no longer requires touching ten other files. The description drives everything, and the serialization, comparison, and merge logic derive from it automatically. <a href="https://www.boost.org/doc/libs/release/libs/describe/">Boost.Describe</a> and <a href="https://www.boost.org/doc/libs/release/libs/mp11/">Boost.Mp11</a> are private dependencies that do not appear in public headers.</p>
<p>Along the way, Gennaro also added <strong><a href="https://github.com/cppalliance/mrdocs/pull/1163">function object support</a></strong>, fixed <strong><a href="https://github.com/cppalliance/mrdocs/pull/1157">Markdown inline formatting</a></strong> and <strong><a href="https://github.com/cppalliance/mrdocs/pull/1173">missing dependent array bounds</a></strong>.</p>
<details>
<summary>Reflection and metadata commits</summary>
<p><strong>Reflection (Gennaro Prota)</strong></p>
<ul>
<li><a href="https://github.com/cppalliance/mrdocs/commit/d490880">d490880</a> refactor(metadata): integrate Boost.Describe</li>
<li><a href="https://github.com/cppalliance/mrdocs/commit/c4dd89a">c4dd89a</a> feat: add $meta.type and $meta.bases to all DOM objects</li>
<li><a href="https://github.com/cppalliance/mrdocs/commit/d4a64ef">d4a64ef</a> fix: replace the XML generator with a reflection-based one</li>
<li><a href="https://github.com/cppalliance/mrdocs/commit/6ce961f">6ce961f</a> refactor: add custom reflection facilities (MrDocs.Describe)</li>
<li><a href="https://github.com/cppalliance/mrdocs/commit/eb68494">eb68494</a> refactor: migrate all reflection consumers to MrDocs.Describe</li>
<li><a href="https://github.com/cppalliance/mrdocs/commit/8f5391b">8f5391b</a> refactor: replace per-type merge() one-liners with a single generic template</li>
<li><a href="https://github.com/cppalliance/mrdocs/commit/e749144">e749144</a> feat: make the reflection consumers public</li>
<li><a href="https://github.com/cppalliance/mrdocs/commit/1ed76ad">1ed76ad</a> refactor: replace most per-type tag_invoke overloads with a single generic template</li>
<li><a href="https://github.com/cppalliance/mrdocs/commit/0246935">0246935</a> refactor: replace per-type operator==() and operator&lt;=&gt;() with a single generic template</li>
</ul>
<p><strong>Features and fixes (Gennaro Prota)</strong></p>
<ul>
<li><a href="https://github.com/cppalliance/mrdocs/commit/93a5032">93a5032</a> feat: add function object support</li>
<li><a href="https://github.com/cppalliance/mrdocs/commit/f35ebcd">f35ebcd</a> fix: rendering of Markdown inline formatting and bullet lists</li>
<li><a href="https://github.com/cppalliance/mrdocs/commit/4ae305b">4ae305b</a> fix: missing dependent array bounds in the output</li>
<li><a href="https://github.com/cppalliance/mrdocs/commit/72fba40">72fba40</a> test: add golden tests for a partial class template specialization</li>
</ul>
</details>
<blockquote>
<p>The reflection work is the foundation for everything that comes next: the extension system, the upcoming Lua scripting, and the metadata transformation pipeline.</p>
</blockquote>
<h2 id="first-steps-toward-extensions">First Steps Toward Extensions</h2>
<p>MrDocs supports two extension points: <strong>JavaScript</strong> for Handlebars template helpers, and <strong>Lua</strong> for more powerful scripting. The JavaScript engine had been <a href="https://duktape.org/">Duktape</a>, but Duktape is no longer actively maintained and only supports ES5.1. We needed a replacement.</p>
<p>We evaluated several alternatives (<a href="https://github.com/cppalliance/mrdocs/issues/881">#881</a>):</p>
<table>
<thead>
<tr>
<th>Engine</th>
<th>JS Support</th>
<th>Windows/MSVC</th>
<th>Size</th>
<th>License</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="https://github.com/bellard/quickjs">QuickJS</a></td>
<td>ES2023</td>
<td>No (clang-cl only)</td>
<td>~370 KB</td>
<td>MIT</td>
</tr>
<tr>
<td><a href="https://github.com/lynx-family/primjs">PrimJS</a></td>
<td>ES2019</td>
<td>No (POSIX only)</td>
<td>~370 KB</td>
<td>MIT</td>
</tr>
<tr>
<td><a href="https://jerryscript.net/">JerryScript</a></td>
<td>ES5.1 + ES2022 subset</td>
<td>Yes</td>
<td>~200 KB</td>
<td>Apache 2.0</td>
</tr>
<tr>
<td><a href="https://github.com/Samsung/escargot">Escargot</a></td>
<td>ES2025 subset</td>
<td>Yes</td>
<td>~400-500 KB</td>
<td>LGPL 2.1</td>
</tr>
<tr>
<td><a href="https://github.com/ArtifexSoftware/mujs">MuJS</a></td>
<td>ES5.1</td>
<td>Yes</td>
<td>~200-300 KB</td>
<td>ISC</td>
</tr>
<tr>
<td><a href="https://github.com/Moddable-OpenSource/moddable">Moddable XS</a></td>
<td>ES2025 (~99%)</td>
<td>Yes (via SDK)</td>
<td>~100-300 KB</td>
<td>Apache/GPL/LGPL</td>
</tr>
<tr>
<td><a href="https://github.com/cesanta/mjs">mJS</a></td>
<td>Restricted ES6</td>
<td>Yes</td>
<td>~50-60 KB</td>
<td>GPL 2.0 / Commercial</td>
</tr>
<tr>
<td><a href="https://github.com/cesanta/elk">Elk</a></td>
<td>Minimal ES6</td>
<td>Yes</td>
<td>~20-30 KB</td>
<td>GPL 2.0 / Commercial</td>
</tr>
</tbody>
</table>
<p>We first experimented with <strong>QuickJS</strong>, which had the best ES support. But it requires C11 features like <code>&lt;stdatomic.h&gt;</code> and <code>__int128</code> that plain MSVC does not support. On Windows, users would need Clang with the Visual Studio runtime. <strong>PrimJS</strong> was POSIX-only. We settled on <strong><a href="https://jerryscript.net/">JerryScript</a></strong>: it supports Windows and MSVC natively, has a small footprint (~200 KB), and covers enough of ES2022 for template helpers. Unlike most alternatives in the table, JerryScript was designed from the ground up to be <strong>embedded</strong> in other applications, which makes it more like <a href="https://www.lua.org/">Lua</a> and less like engines that target browsers or standalone runtimes.</p>
<p>The <strong><a href="https://github.com/cppalliance/mrdocs/pull/1126">JavaScript helpers extension</a></strong> was a single commit but a large one: <strong>85 files changed, 4,287 insertions</strong>. The work included:</p>
<ul>
<li><strong>Replacing Duktape with JerryScript</strong> across the entire codebase, including build scripts, CMake recipes, and third-party patches</li>
<li><strong>Rewriting the C++ JavaScript bindings</strong> (<code>JavaScript.hpp</code> and <code>JavaScript.cpp</code>) with shared context lifetime, safer value accessors, and clearer error messages</li>
<li><strong>A layered addon system</strong> where projects provide JavaScript helpers in a directory structure (<code>generator/common/helpers/</code> for shared helpers, <code>generator/html/helpers/</code> for format-specific ones). Multiple addon directories can be layered, so a project’s helpers override or extend the defaults.</li>
<li><strong>Golden tests</strong> for extension output (<code>js-helper/</code>, <code>js-helper-layering/</code>) to verify that helpers produce the expected documentation</li>
<li><strong>1,335 lines of new JavaScript binding tests</strong> covering the engine lifecycle, value conversion, error handling, and helper registration</li>
</ul>
<p>Combined with the <strong><a href="https://github.com/cppalliance/mrdocs/pull/1139">public API for registering custom generators</a></strong>, MrDocs now supports customization beyond templates. A library like <a href="https://develop.capy.cpp.al/capy/reference/boost/capy.html">Boost.Capy</a> could write an extension that transforms its coroutine types into function documentation, without any changes to MrDocs itself.</p>
<div class="mermaid">
%%{init: {"theme": "base", "themeVariables": {"primaryColor": "#f7f9ff", "primaryBorderColor": "#9aa7e8", "primaryTextColor": "#1f2a44", "lineColor": "#b4bef2", "secondaryColor": "#fbf8ff", "tertiaryColor": "#ffffff", "fontSize": "14px"}}}%%
flowchart LR
A[Clang AST] --&gt; B[Extraction]
B --&gt; C[Corpus]
C --&gt; D[Transformation Extensions]
D --&gt; E[Handlebars Generators]
E --&gt; F[Documentation Templates]
F --&gt; H[HTML / AsciiDoc]
F --&gt; G[Template Extensions]
G --&gt; F
D -.-&gt; I[XML]
</div>
<p>The vision for extensions has two layers:</p>
<ul>
<li><strong>Transformation extensions</strong> operate on the corpus between extraction and generation. A library could transform its internal types into the documentation structure it wants. This layer is not yet implemented.</li>
<li><strong>Template extensions</strong> (JavaScript helpers) operate inside the Handlebars templates that produce HTML and AsciiDoc output. This is the layer we shipped.</li>
<li><strong>Lua scripts</strong> for more powerful scripting in both layers</li>
</ul>
<details>
<summary>Extension and generator commits</summary>
<ul>
<li><a href="https://github.com/cppalliance/mrdocs/commit/0f3ecb4">0f3ecb4</a> feat: javascript helpers extension (85 files, 4,287 insertions)</li>
<li><a href="https://github.com/cppalliance/mrdocs/commit/930a5ea">930a5ea</a> fix: jerry_port_context_free wrong signature causes silent corruption</li>
<li><a href="https://github.com/cppalliance/mrdocs/commit/8da0930">8da0930</a> feat(lib): public API for generator registration</li>
<li><a href="https://github.com/cppalliance/mrdocs/commit/788c1ba">788c1ba</a> feat(generators): tables for symbols have headers</li>
</ul>
</details>
<h2 id="why-we-discarded-mrdocs-as-compiler">Why We Discarded MrDocs-as-Compiler</h2>
<p>One unknown we explored and <strong>deliberately discarded</strong> was <a href="https://github.com/cppalliance/mrdocs/issues/1073">MrDocs-as-compiler</a> (<a href="https://github.com/cppalliance/mrdocs/issues/1073">#1073</a>). The idea, proposed by <a href="https://github.com/pdimov">Peter Dimov</a>, was to treat MrDocs like a compiler: emit “object” files per translation unit, then “link” them to produce the final reference. <a href="https://cmake.org/">CMake</a> would invoke MrDocs as if it were <a href="https://clang.llvm.org/">Clang</a>, with identical command-line options.</p>
<p>We spent time studying tools that work this way: <a href="https://clang.llvm.org/extra/clang-tidy/">clang-tidy</a>, <a href="https://clang.llvm.org/extra/clang-doc/">clang-doc</a>, <a href="https://include-what-you-use.org/">include-what-you-use</a>. What we found is that <strong>tricking CMake into thinking MrDocs is a real compiler</strong> is not trivial. Every tool that tries this approach ends up needing either a coordinator binary (reimplementing what MrDocs already has) or CMake helper scripts. Both add workflows rather than simplifying them.</p>
<p>The experience from the Boost ecosystem reinforced this: no Boost project uses any of these compiler-like tools for static analysis, and the reason is complexity. People who find the compilation database workflow too involved are going to be even less inclined to adopt a tool that requires them to pretend to be a compiler. We decided to keep MrDocs as a <strong>single-step tool</strong> that reads a compilation database and produces output, rather than splitting it into a multi-binary pipeline that would need its own coordination layer.</p>
<h1 id="contributor-experience">Contributor Experience</h1>
<p>As more people contributed to MrDocs, the gap between “clone the repo” and “submit a useful PR” needed closing. The biggest change was the <strong><a href="/alan/2026/04/15/Alan.html">bootstrap script</a></strong>, which reduced the entire build setup to a single <code>python bootstrap.py</code> command (covered in a <a href="/alan/2026/04/15/Alan.html">separate post</a>). Beyond the bootstrap, we <strong>split the contributor guide</strong> into focused sections, added <strong>reference documentation for MrDocs comment syntax</strong> (so contributors know what <code>@copydoc</code>, <code>@see</code>, and other commands do), and created a <strong><code>run_all_tests</code> script</strong> that runs the full test suite locally without needing to understand the CMake test configuration.</p>
<details>
<summary>Onboarding commits</summary>
<ul>
<li><a href="https://github.com/cppalliance/mrdocs/commit/b103cba">b103cba</a> docs(reference): mrdocs comments</li>
<li><a href="https://github.com/cppalliance/mrdocs/commit/9b7ec24">9b7ec24</a> feat(util): run_all_tests script</li>
<li><a href="https://github.com/cppalliance/mrdocs/commit/5902699">5902699</a> docs: update packages</li>
<li><a href="https://github.com/cppalliance/mrdocs/commit/302f0a6">302f0a6</a> docs: split contribute.adoc guide</li>
</ul>
</details>
<h2 id="automating-pr-reviews">Automating PR Reviews</h2>
<p>MrDocs PRs tend to be <strong>large and hard to review</strong>. A single PR might touch the AST visitor, the Handlebars templates, the Antora extension, the CI configuration, and hundreds of <strong>golden test files</strong> (when an intentional change to the output format updates the expected output for every test case). We found ourselves making the same review comments over and over.</p>
<p>We set up <strong><a href="https://danger.systems/js/">Danger.js</a></strong> to catch these patterns before human reviewers see the PR. The most important check is <strong>detecting when source code changes do not include corresponding tests</strong>: if someone changes extraction logic but does not update the golden tests, or changes a template without updating the expected output, Danger flags it. Beyond that:</p>
<ul>
<li><strong>Categorizes</strong> all file changes into scopes (source, tests, golden-tests, docs, CI, build, tooling) and generates a <strong>summary table</strong> showing churn per scope</li>
<li><strong>Validates</strong> commit messages against <a href="https://www.conventionalcommits.org/">Conventional Commits</a> format</li>
<li><strong>Warns</strong> when a single commit exceeds <strong>2,000 lines</strong> of source churn (encouraging smaller, reviewable slices)</li>
<li><strong>Flags</strong> mismatched commit types (e.g., a <code>feat:</code> commit that only touches test files suggests <code>test:</code> instead)</li>
<li><strong>Rejects</strong> PR descriptions under 40 characters</li>
<li><strong>Ignores</strong> the test check for refactor-only PRs where the tests are expected to remain unchanged</li>
</ul>
<p>Even when there are no warnings, the <strong>scope summary table</strong> gives reviewers an immediate sense of what a large PR touches. On a PR that changes 500 lines of source and 3,000 lines of golden tests, the table makes it clear that the bulk of the diff is expected test output, not new logic.</p>
<details>
<summary>Danger.js commits</summary>
<ul>
<li><a href="https://github.com/cppalliance/mrdocs/commit/6f5f6e9">6f5f6e9</a> ci: setup danger.js</li>
<li><a href="https://github.com/cppalliance/mrdocs/commit/5429b2e">5429b2e</a> ci(danger): align report table and add top-files summary</li>
<li><a href="https://github.com/cppalliance/mrdocs/commit/240921d">240921d</a> ci(danger): split PR target ci workflows</li>
<li><a href="https://github.com/cppalliance/mrdocs/commit/08c46b6">08c46b6</a> ci(danger): correct file delta calculation in reports</li>
<li><a href="https://github.com/cppalliance/mrdocs/commit/2cfd081">2cfd081</a> ci(danger): adjust large commit threshold</li>
<li><a href="https://github.com/cppalliance/mrdocs/commit/71845c8">71845c8</a> ci(danger): map root files into explicit scopes</li>
<li><a href="https://github.com/cppalliance/mrdocs/commit/17b0a57">17b0a57</a> ci(danger): ignore test check for refactor-only PRs</li>
<li><a href="https://github.com/cppalliance/mrdocs/commit/6481fd3">6481fd3</a> ci(danger): simplify CI naming</li>
<li><a href="https://github.com/cppalliance/mrdocs/commit/fd7d248">fd7d248</a> ci(danger): omit empty sections from report</li>
<li><a href="https://github.com/cppalliance/mrdocs/commit/7502961">7502961</a> ci(danger): categorize util/bootstrap as build scope</li>
<li><a href="https://github.com/cppalliance/mrdocs/commit/57e191e">57e191e</a> ci(danger): better markdown format</li>
</ul>
</details>
<h2 id="ci-infrastructure">CI Infrastructure</h2>
<p>We integrated <strong><a href="https://codecov.io/">Codecov</a></strong> for tracking test coverage across PRs and switched from GCC to <strong>Clang for coverage</strong> (more accurate AST-based measurement). CI speed was a recurring concern: we <strong>skipped remote documentation generation on PRs</strong>, <strong>sped up release demos</strong>, and <strong>skipped long tests</strong> that were not catching new bugs. LLVM cache keys were <strong>unified</strong> to avoid redundant builds, and CTest timeouts were increased for sanitizer jobs that run significantly slower. <strong><a href="https://github.com/mizvekov">Matheus Izvekov</a></strong> contributed the <a href="https://github.com/cppalliance/mrdocs/pull/1144">Clang coverage switch</a>, fixed an <a href="https://github.com/cppalliance/mrdocs/pull/1132">infinite recursion in extraction</a>, and moved the project to <a href="https://github.com/cppalliance/mrdocs/pull/1077">use system libs by default</a>.</p>
<details>
<summary>CI infrastructure commits</summary>
<ul>
<li><a href="https://github.com/cppalliance/mrdocs/commit/ed6b3bc">ed6b3bc</a> ci: add codecov configuration</li>
<li><a href="https://github.com/cppalliance/mrdocs/commit/5426a0a">5426a0a</a> ci: use clang for coverage</li>
<li><a href="https://github.com/cppalliance/mrdocs/commit/d629173">d629173</a> fix(ci): unify redundant LLVM cache keys</li>
<li><a href="https://github.com/cppalliance/mrdocs/commit/36a3b51">36a3b51</a> ci: update actions to v1.9.1</li>
<li><a href="https://github.com/cppalliance/mrdocs/commit/7b2103a">7b2103a</a> ci: increase CTest timeout for MSan jobs</li>
<li><a href="https://github.com/cppalliance/mrdocs/commit/086becc">086becc</a> ci: increase the ctest timeout to 9000</li>
<li><a href="https://github.com/cppalliance/mrdocs/commit/adb6821">adb6821</a> ci(cpp-matrix): remove the optimized-debug factor</li>
<li><a href="https://github.com/cppalliance/mrdocs/commit/9507a38">9507a38</a> ci: simplify CI workflow and upgrade cpp-actions to @develop</li>
<li><a href="https://github.com/cppalliance/mrdocs/commit/9a5bd3c">9a5bd3c</a> ci: skip remote documentation generation on PRs</li>
<li><a href="https://github.com/cppalliance/mrdocs/commit/637011f">637011f</a> ci: detect and report demo generation failures</li>
<li><a href="https://github.com/cppalliance/mrdocs/commit/084322d">084322d</a> ci: speed up release demos on PRs</li>
<li><a href="https://github.com/cppalliance/mrdocs/commit/471951d">471951d</a> ci: skip long tests to speed up CI</li>
<li><a href="https://github.com/cppalliance/mrdocs/commit/a5f160b">a5f160b</a> ci: increase test coverage for the new XML generator</li>
<li><a href="https://github.com/cppalliance/mrdocs/commit/b1fc43c">b1fc43c</a> ci: exclude Reflection.hpp from coverage</li>
<li><a href="https://github.com/cppalliance/mrdocs/commit/a1f9a82">a1f9a82</a> ci: accept any g++-14 version</li>
<li><a href="https://github.com/cppalliance/mrdocs/commit/c136a46">c136a46</a> ci(website): preserve roadmap directory during deployment</li>
<li><a href="https://github.com/cppalliance/mrdocs/commit/4763d86">4763d86</a> revert(ci): remove premature roadmap report step</li>
<li><a href="https://github.com/cppalliance/mrdocs/commit/3462996">3462996</a> ci: revert coverage changes</li>
<li><a href="https://github.com/cppalliance/mrdocs/commit/8b2c3e9">8b2c3e9</a> ci: align llvm-sanitizer-config with archive basename</li>
<li><a href="https://github.com/cppalliance/mrdocs/commit/fdff573">fdff573</a> ci: gitignore CI node_modules</li>
<li><a href="https://github.com/cppalliance/mrdocs/commit/757d446">757d446</a> fix(ci): update the fmt branch reference from master to main</li>
<li><a href="https://github.com/cppalliance/mrdocs/commit/a3366b0">a3366b0</a> fix(ci): name rolling release packages after the branch</li>
</ul>
</details>
<h2 id="test-infrastructure">Test Infrastructure</h2>
<p>MrDocs uses <strong>golden tests</strong>: the expected output for every test case is stored as a file, and the test runner compares the actual output against it. The most important change was adding <strong>multipage golden tests</strong>. Previously, all golden tests were single-page, but many bugs only manifested in multi-page output (cross-references between pages, navigation links, index generation). We were missing these entirely because we had no way to test them. We also added <strong>output normalization</strong> (so platform differences do not cause false failures) and <strong>regression categories</strong> so tests can be grouped and run selectively. A <strong><code>run_ci_with_act.py</code></strong> script lets contributors run the full CI pipeline locally using <a href="https://github.com/nektos/act">act</a>.</p>
<details>
<summary>Test infrastructure commits</summary>
<ul>
<li><a href="https://github.com/cppalliance/mrdocs/commit/bf78b1b">bf78b1b</a> test: support multipage golden tests</li>
<li><a href="https://github.com/cppalliance/mrdocs/commit/d7ad1ce">d7ad1ce</a> test: output normalization</li>
<li><a href="https://github.com/cppalliance/mrdocs/commit/ccd7f71">ccd7f71</a> test: check int tests results in ctest</li>
<li><a href="https://github.com/cppalliance/mrdocs/commit/681b0cd">681b0cd</a> chore: assign categories to regression tests</li>
<li><a href="https://github.com/cppalliance/mrdocs/commit/9146125">9146125</a> test: cover additional paths in DocCommentFinalizer.cpp</li>
<li><a href="https://github.com/cppalliance/mrdocs/commit/8326417">8326417</a> test: run_ci_with_act.py script</li>
<li><a href="https://github.com/cppalliance/mrdocs/commit/5527e9c">5527e9c</a> test: testClang_stdCxx default is C++26</li>
<li><a href="https://github.com/cppalliance/mrdocs/commit/0dfdb02">0dfdb02</a> test: –bad is disabled by default</li>
</ul>
</details>
<h1 id="acknowledgments-and-reflections">Acknowledgments and Reflections</h1>
<p>Going into the wild changed MrDocs. The edge cases, the customization requests, and the integration feedback shaped the direction more than any internal roadmap could.</p>
<p><strong><a href="https://github.com/gennaroprota">Gennaro Prota</a></strong> drove the reflection integration that reduces maintenance burden across the entire codebase. <strong><a href="https://github.com/mizvekov">Matheus Izvekov</a></strong> hardened CI with coverage, sanitizers, and warnings-as-errors, and migrated dependency management to the bootstrap script. <strong><a href="https://github.com/julioest">Julio Estrada</a></strong> and <strong><a href="https://github.com/rbbeeston">Robert Beeston</a></strong> delivered the polished public face of MrDocs. <strong><a href="https://github.com/K-ballo">Agustín Bergé</a></strong> contributed AST and metadata fixes including base member shadowing and alias SFINAE detection. <strong><a href="https://github.com/jll63">Jean-Louis Leroy</a></strong> provided detailed feedback from <a href="https://github.com/boostorg/openmethod">Boost.OpenMethod</a> that drove multiple improvements.</p>
<p>The most requested feature we have not solved yet is <strong>macro support</strong> (<a href="https://github.com/cppalliance/mrdocs/issues/1127">#1127</a>). Macros are expanded before parsing and do not appear in the <a href="https://en.wikipedia.org/wiki/Abstract_syntax_tree">AST</a>. Supporting them would require preprocessor-level integration with <a href="https://clang.llvm.org/">Clang</a>. The work ahead also includes <strong>Lua scripting</strong>, <strong>metadata transforms</strong>, and <strong>deeper reflection</strong>, all direct responses to what users told us they need.</p>
<p>The biggest lesson from this period is that the problems worth solving are the ones users bring. We spent time on an unknowns framework to decide what to explore, but the most impactful work came from people who showed up with a broken demo page, a missing breadcrumb, or a duplicate ellipsis in their generated docs.</p>
<p>The complete set of changes is available in the <a href="https://github.com/cppalliance/mrdocs">MrDocs repository</a>.</p></content><author><name></name></author><category term="alan" /><summary type="html">The questions changed. For a long time, people asked about MrDocs in the abstract: what formats will it support, how will it handle templates, when will it be ready. Then, gradually, the questions became specific. Jean-Louis Leroy, the author of Boost.OpenMethod, became one of our most active sources of feedback. His library exercises corners of C++ that most projects never touch, which means MrDocs gets tested in ways we would not have anticipated. He wanted to know why his template specializations were not sorted correctly. He wanted macro support because Boost libraries rely heavily on macros. He hit a crash when his doc comments contained HTML tables. These are not theoretical questions about a tool that might exist someday. These are questions from someone who already generated documentation with MrDocs and needs it to work better. In our previous post, we described MrDocs transitioning from prototype to product. This post is about what happened when MrDocs went into the wild. Real Projects, Real Problems The Demo Page Breadcrumbs Without a Navigation File Coordinating Two Independent Extensions Edge Cases in the Wild Rendering and Output Under the Hood The MrDocs Website Exploring the Unknowns Reflection: Replacing Boilerplate with Introspection First Steps Toward Extensions Why We Discarded MrDocs-as-Compiler Contributor Experience Automating PR Reviews CI Infrastructure Test Infrastructure Acknowledgments and Reflections Real Projects, Real Problems %%{init: {"theme": "base", "themeVariables": {"primaryColor": "#e4eee8", "primaryBorderColor": "#affbd6", "primaryTextColor": "#000000", "lineColor": "#baf9d9", "secondaryColor": "#f0eae4", "tertiaryColor": "#ebeaf4", "fontSize": "14px"}}}%% mindmap root((Feedback)) First impressions Unstyled demos Custom stylesheets Navigation Orphaned pages Breadcrumbs AST edge cases Parameter packs Friend targets Detail namespaces Rendering Description ordering Code blocks Anchor links Runtime JS engine switch Compiler fallback The Demo Page Right after the previous post, where we announced the MVP and encouraged people to try MrDocs, we noticed the demos page was not doing us any favors. Someone shared MrDocs on a developer community and the website started getting traffic. The landing page looked polished, but visitors clicked through to the demos and saw raw, unstyled HTML: no fonts, no spacing, no colors. The HTML generator produced correct semantic markup, and that is technically the point: users are supposed to customize the output with their own stylesheets. But on the demos page, there was no stylesheet at all, and the result looked broken rather than customizable. The custom stylesheet system added five configuration options (stylesheets, linkcss, copycss, no-default-styles, stylesdir) so projects can match their own branding. A bundled default CSS now ships with MrDocs, and it was refined to remove gradients in favor of solid, readable backgrounds. Stylesheet commits 5fe30c1 feat: custom stylesheets 33d985c chore: version is 0.8.0 Breadcrumbs Without a Navigation File MrDocs generates thousands of reference pages, one per C++ symbol. We maintain an Antora extension, the antora-cpp-reference-extension, that integrates these pages into Antora-based documentation sites. But the generated pages end up orphaned from the navigation tree. Users found the navigation confusing: clicking on “boost” in the breadcrumb did not go where expected, and reference pages had no trail showing where they belonged in the hierarchy. The obvious fix would be to list every page in Antora’s nav.adoc, but maintaining a navigation file with thousands of entries that changes every time a symbol is added or removed is not practical. Worse, Antora renders the navigation file in the sidebar, so listing every reference page would flood the UI with thousands of entries. We discussed the problem extensively with the Antora maintainer on the Antora community chat. His position was clear: Antora was designed so that pages must be in the navigation file. Programmatic editing of navigation is not supported. That was not acceptable for us. We needed breadcrumbs that work for thousands of generated pages without polluting the sidebar or requiring a hand-maintained navigation file. The Antora author’s position was reasonable from his perspective (Antora is a general-purpose documentation tool, not a reference generator), but our use case was fundamentally different from what Antora was designed for. The antora-cpp-reference-extension now builds breadcrumbs independently from the navigation file. MrDocs generates reference pages in a directory structure that mirrors the C++ namespace hierarchy (boost/urls/segments_view.adoc lives inside boost/urls/). The extension uses this structure to reconstruct the breadcrumb trail: each directory maps to a namespace, and the page title (which is the symbol name) becomes the last breadcrumb entry. The result reads naturally: Reference &gt; boost &gt; urls &gt; segments_view. Zero changes to the nav file. The sidebar stays clean. Breadcrumbs appear automatically and update when symbols are added or removed. Breadcrumb and reference extension commits antora-cpp-reference-extension ae95eb2 feat: synthesize reference breadcrumbs without nav files 10a4019 feat: add auto base URL detection 6a6c08b docs: auto-base-url option 4f7c79f refactor: enhance release asset validation Coordinating Two Independent Extensions The antora-cpp-reference-extension generates reference pages and breadcrumbs. The antora-cpp-tagfiles-extension resolves cross-library symbol links (so a reference to boost::system::error_code in Boost.URL’s docs links to the correct page in Boost.System’s docs). These are two independent Antora extensions running as separate jobs. The problem was that the reference extension generates tagfiles as a side effect of producing reference pages, and the tagfiles extension needs the most recent version of those tagfiles to resolve links correctly. MrDocs changes the tagfiles every time the corpus changes. Manually keeping them in sync was not sustainable: committing tagfiles to the repository meant they were always stale by the time the next build ran. We made the extensions coordinate directly. The reference extension now hands its tagfile to the tagfiles extension at build time, so the links always reflect the current state of the documentation. The reference extension also gained auto base URL detection, removing the need for manual path configuration when switching between development and production builds. Extension coordination commits antora-cpp-reference-extension 6e8ffcb feat: antora-cpp-tagfiles-extension coordination 8f12576 chore: version is 0.1.0 antora-cpp-tagfiles-extension 98eba40 feat: antora-cpp-reference-extension coordination 5a1723c feat: add global log level control for missing symbols 453f01b chore: version is 0.1.0 Edge Cases in the Wild As more libraries adopted MrDocs, edge cases in C++ symbol extraction surfaced. Boost.Beast exposed a duplicate ellipsis in parameter pack rendering (#1108, #1129): Before: T&amp; emplace(Args...&amp;&amp;... args) After: T&amp; emplace(Args&amp;&amp;... args) Boost.OpenMethod revealed that friend targets were not resolving correctly. Boost.Buffers uncovered a problem with detail namespaces: when a class inherits from a base in a hidden namespace, the inherited members appeared in the documentation but their doc comments were lost (#1107). We fixed this so derived classes inherit documentation from hidden bases. Unnamed structs also sparked an extended design discussion. When C++ code declares constexpr struct {} f{};, MrDocs needs a stable, unique name for hyperlinks. The team established a collaborative design process using shared documents, with Peter Dimov contributing an insight about C compatibility (typedef struct {} T; makes the struct named in C++). AST and metadata commits c85be75 fix: remove duplicate ellipsis in parameter pack expansion c3dbded fix(ast): prevent TU parent from including unmatched globals 76b7b43 fix(ast): canonicalize friend targets 05f5852 fix(metadata): copy impl-defined base docs 35cf1f6 fix: UsingSymbol is SymbolParent c406d57 fix: preserve extraction mode when copying members from derived classes 4e7ef04 fix: prevent infinite recursion when extracting non-regular base class 0a69301 fix: extract and fix some special member function helpers Rendering and Output Users noticed that the manual description of a symbol was buried below long member tables (#1105). On a class with many members, you had to scroll past the entire member listing before finding the author’s explanation of what the class does. We moved the description to appear immediately after the synopsis, matching what cppreference does. Other rendering issues included HTML code blocks not wrapped in &lt;pre&gt; tags, anchor links appearing when the wrapper element was missing, and the Handlebars template engine accumulating special name re-mappings that conflated different symbols. Rendering and output commits d90eae6 fix: hide anchor links when wrapper is not included 92491de fix: manual description comes before member lists 58bf524 fix: remove all special name re-mappings for Handlebars 2a75692 fix: HTML code blocks not wrapped in pre tags 1ebff32 fix: bottomUpTraverse() skips ListBlock items 7b118b1 fix: missing @return command in doc comment Under the Hood We fixed a compiler fallback issue where MrDocs failed when the compilation database referenced a compiler that was not available on the current machine, and corrected sanitizer flag propagation so that UBSan and TSan do not unnecessarily propagate to dependency builds. Build and toolchain commits 235f5c8 fix: fall back to system compilers when database compiler is unavailable f320581 fix: don’t pass sanitizer to dependency builds for UBSan/TSan The MrDocs Website While we were fixing the generated output, Robert Beeston and Julio Estrada were redesigning the MrDocs website. Robert led the design direction, working with a team to develop a visual identity that balances a distinctive retro aesthetic with modern readability, including a dark theme. Julio handled the implementation: mobile-responsive layout, UI styling improvements, cleaner backgrounds and styles, Open Graph and Twitter meta tags for social sharing, and a close button for the docs navigation on smaller screens. For a documentation tool, the website is the first thing potential users see. Having a polished, memorable landing page matters more than it might for other kinds of projects. Exploring the Unknowns The team made a deliberate choice: instead of following a traditional feature roadmap, we would focus on areas of uncertainty (#1113). These were open questions that blocked multiple design decisions at once: MrDocs-as-compiler (#1073): should MrDocs emit “object” files for later “linking,” like a compiler? Scripting extensions (#1128, #881): how should users extend and transform documentation output? Plugins (#58, #1044): how should third-party code register new generators? JSON-only MrDocs: should we add a JSON output format alongside (or replacing) the existing XML structured output? Reflection (#1114): how do we reduce the maintenance burden of the growing metadata model? Cross-linking (#1072): how do we reference symbols in other libraries? The motivation was practical. Each Boost library that adopted MrDocs had its own needs that could not be met by the core tool alone. Boost.URL has implementation_defined namespaces with internal code that should be hidden or transformed in the documentation. Boost.Capy has detail types that should be presented as user-facing types. Coroutines are represented as types in the AST but should be documented as functions. We want MrDocs to be smart enough, with project-specific extensions, that library authors do not have to do workarounds in the source code just to get the documentation right. Rather than hard-coding solutions for each library, the unknowns framework asked: what general mechanisms would let every library solve its own documentation problems? %%{init: {"theme": "base", "themeVariables": {"primaryColor": "#f7f9ff", "primaryBorderColor": "#9aa7e8", "primaryTextColor": "#1f2a44", "lineColor": "#b4bef2", "secondaryColor": "#fbf8ff", "tertiaryColor": "#ffffff", "fontSize": "14px"}}}%% mindmap root((Unknowns)) Scripting extensions JS helpers Lua Plugins Generator API DLL loading Reflection Boost.Describe MrDocs.Describe Cross-linking Tagfiles Antora coordination JSON-only MrDocs MrDocs-as-compiler Reflection: Replacing Boilerplate with Introspection MrDocs models many kinds of C++ symbols: functions, classes, namespaces, enums, typedefs, concepts, and more. Each symbol type has metadata, and every piece of code that touches that metadata had to enumerate all fields by hand. Adding a single field to a symbol type meant updating it in: Schema files that describe the metadata format Generators (HTML, AsciiDoc, XML) that produce the output Templates that render individual pages Operators like comparison functions, merge functions (e.g., merging symbols from different translation units when only one is documented), and equality checks Documentation describing the metadata The code itself that populates and transforms the metadata That is roughly ten to fifteen places per field, and missing one caused CI failures that blocked everyone. This was one of the unknowns we identified: how to reduce the maintenance burden as the data model grows. Worse, downstream users who had their own templates and extensions also had to learn about the new fields and update everything accordingly. Gennaro Prota, with his strong background in generic programming and metaprogramming, took ownership of the reflection problem. The work progressed through several stages: Integrate Boost.Describe into the metadata system, replacing hand-written serialization functions Add $meta.type and $meta.bases to all DOM objects so templates can introspect the corpus Replace the XML generator with a reflection-based one (no more hand-maintained XML output) Build a custom reflection system (MrDocs.Describe) tailored to our needs Replace per-type operators with a single generic template The result eliminated the second step entirely: adding a new field to a symbol type no longer requires touching ten other files. The description drives everything, and the serialization, comparison, and merge logic derive from it automatically. Boost.Describe and Boost.Mp11 are private dependencies that do not appear in public headers. Along the way, Gennaro also added function object support, fixed Markdown inline formatting and missing dependent array bounds. Reflection and metadata commits Reflection (Gennaro Prota) d490880 refactor(metadata): integrate Boost.Describe c4dd89a feat: add $meta.type and $meta.bases to all DOM objects d4a64ef fix: replace the XML generator with a reflection-based one 6ce961f refactor: add custom reflection facilities (MrDocs.Describe) eb68494 refactor: migrate all reflection consumers to MrDocs.Describe 8f5391b refactor: replace per-type merge() one-liners with a single generic template e749144 feat: make the reflection consumers public 1ed76ad refactor: replace most per-type tag_invoke overloads with a single generic template 0246935 refactor: replace per-type operator==() and operator&lt;=&gt;() with a single generic template Features and fixes (Gennaro Prota) 93a5032 feat: add function object support f35ebcd fix: rendering of Markdown inline formatting and bullet lists 4ae305b fix: missing dependent array bounds in the output 72fba40 test: add golden tests for a partial class template specialization The reflection work is the foundation for everything that comes next: the extension system, the upcoming Lua scripting, and the metadata transformation pipeline. First Steps Toward Extensions MrDocs supports two extension points: JavaScript for Handlebars template helpers, and Lua for more powerful scripting. The JavaScript engine had been Duktape, but Duktape is no longer actively maintained and only supports ES5.1. We needed a replacement. We evaluated several alternatives (#881): Engine JS Support Windows/MSVC Size License QuickJS ES2023 No (clang-cl only) ~370 KB MIT PrimJS ES2019 No (POSIX only) ~370 KB MIT JerryScript ES5.1 + ES2022 subset Yes ~200 KB Apache 2.0 Escargot ES2025 subset Yes ~400-500 KB LGPL 2.1 MuJS ES5.1 Yes ~200-300 KB ISC Moddable XS ES2025 (~99%) Yes (via SDK) ~100-300 KB Apache/GPL/LGPL mJS Restricted ES6 Yes ~50-60 KB GPL 2.0 / Commercial Elk Minimal ES6 Yes ~20-30 KB GPL 2.0 / Commercial We first experimented with QuickJS, which had the best ES support. But it requires C11 features like &lt;stdatomic.h&gt; and __int128 that plain MSVC does not support. On Windows, users would need Clang with the Visual Studio runtime. PrimJS was POSIX-only. We settled on JerryScript: it supports Windows and MSVC natively, has a small footprint (~200 KB), and covers enough of ES2022 for template helpers. Unlike most alternatives in the table, JerryScript was designed from the ground up to be embedded in other applications, which makes it more like Lua and less like engines that target browsers or standalone runtimes. The JavaScript helpers extension was a single commit but a large one: 85 files changed, 4,287 insertions. The work included: Replacing Duktape with JerryScript across the entire codebase, including build scripts, CMake recipes, and third-party patches Rewriting the C++ JavaScript bindings (JavaScript.hpp and JavaScript.cpp) with shared context lifetime, safer value accessors, and clearer error messages A layered addon system where projects provide JavaScript helpers in a directory structure (generator/common/helpers/ for shared helpers, generator/html/helpers/ for format-specific ones). Multiple addon directories can be layered, so a project’s helpers override or extend the defaults. Golden tests for extension output (js-helper/, js-helper-layering/) to verify that helpers produce the expected documentation 1,335 lines of new JavaScript binding tests covering the engine lifecycle, value conversion, error handling, and helper registration Combined with the public API for registering custom generators, MrDocs now supports customization beyond templates. A library like Boost.Capy could write an extension that transforms its coroutine types into function documentation, without any changes to MrDocs itself. %%{init: {"theme": "base", "themeVariables": {"primaryColor": "#f7f9ff", "primaryBorderColor": "#9aa7e8", "primaryTextColor": "#1f2a44", "lineColor": "#b4bef2", "secondaryColor": "#fbf8ff", "tertiaryColor": "#ffffff", "fontSize": "14px"}}}%% flowchart LR A[Clang AST] --&gt; B[Extraction] B --&gt; C[Corpus] C --&gt; D[Transformation Extensions] D --&gt; E[Handlebars Generators] E --&gt; F[Documentation Templates] F --&gt; H[HTML / AsciiDoc] F --&gt; G[Template Extensions] G --&gt; F D -.-&gt; I[XML] The vision for extensions has two layers: Transformation extensions operate on the corpus between extraction and generation. A library could transform its internal types into the documentation structure it wants. This layer is not yet implemented. Template extensions (JavaScript helpers) operate inside the Handlebars templates that produce HTML and AsciiDoc output. This is the layer we shipped. Lua scripts for more powerful scripting in both layers Extension and generator commits 0f3ecb4 feat: javascript helpers extension (85 files, 4,287 insertions) 930a5ea fix: jerry_port_context_free wrong signature causes silent corruption 8da0930 feat(lib): public API for generator registration 788c1ba feat(generators): tables for symbols have headers Why We Discarded MrDocs-as-Compiler One unknown we explored and deliberately discarded was MrDocs-as-compiler (#1073). The idea, proposed by Peter Dimov, was to treat MrDocs like a compiler: emit “object” files per translation unit, then “link” them to produce the final reference. CMake would invoke MrDocs as if it were Clang, with identical command-line options. We spent time studying tools that work this way: clang-tidy, clang-doc, include-what-you-use. What we found is that tricking CMake into thinking MrDocs is a real compiler is not trivial. Every tool that tries this approach ends up needing either a coordinator binary (reimplementing what MrDocs already has) or CMake helper scripts. Both add workflows rather than simplifying them. The experience from the Boost ecosystem reinforced this: no Boost project uses any of these compiler-like tools for static analysis, and the reason is complexity. People who find the compilation database workflow too involved are going to be even less inclined to adopt a tool that requires them to pretend to be a compiler. We decided to keep MrDocs as a single-step tool that reads a compilation database and produces output, rather than splitting it into a multi-binary pipeline that would need its own coordination layer. Contributor Experience As more people contributed to MrDocs, the gap between “clone the repo” and “submit a useful PR” needed closing. The biggest change was the bootstrap script, which reduced the entire build setup to a single python bootstrap.py command (covered in a separate post). Beyond the bootstrap, we split the contributor guide into focused sections, added reference documentation for MrDocs comment syntax (so contributors know what @copydoc, @see, and other commands do), and created a run_all_tests script that runs the full test suite locally without needing to understand the CMake test configuration. Onboarding commits b103cba docs(reference): mrdocs comments 9b7ec24 feat(util): run_all_tests script 5902699 docs: update packages 302f0a6 docs: split contribute.adoc guide Automating PR Reviews MrDocs PRs tend to be large and hard to review. A single PR might touch the AST visitor, the Handlebars templates, the Antora extension, the CI configuration, and hundreds of golden test files (when an intentional change to the output format updates the expected output for every test case). We found ourselves making the same review comments over and over. We set up Danger.js to catch these patterns before human reviewers see the PR. The most important check is detecting when source code changes do not include corresponding tests: if someone changes extraction logic but does not update the golden tests, or changes a template without updating the expected output, Danger flags it. Beyond that: Categorizes all file changes into scopes (source, tests, golden-tests, docs, CI, build, tooling) and generates a summary table showing churn per scope Validates commit messages against Conventional Commits format Warns when a single commit exceeds 2,000 lines of source churn (encouraging smaller, reviewable slices) Flags mismatched commit types (e.g., a feat: commit that only touches test files suggests test: instead) Rejects PR descriptions under 40 characters Ignores the test check for refactor-only PRs where the tests are expected to remain unchanged Even when there are no warnings, the scope summary table gives reviewers an immediate sense of what a large PR touches. On a PR that changes 500 lines of source and 3,000 lines of golden tests, the table makes it clear that the bulk of the diff is expected test output, not new logic. Danger.js commits 6f5f6e9 ci: setup danger.js 5429b2e ci(danger): align report table and add top-files summary 240921d ci(danger): split PR target ci workflows 08c46b6 ci(danger): correct file delta calculation in reports 2cfd081 ci(danger): adjust large commit threshold 71845c8 ci(danger): map root files into explicit scopes 17b0a57 ci(danger): ignore test check for refactor-only PRs 6481fd3 ci(danger): simplify CI naming fd7d248 ci(danger): omit empty sections from report 7502961 ci(danger): categorize util/bootstrap as build scope 57e191e ci(danger): better markdown format CI Infrastructure We integrated Codecov for tracking test coverage across PRs and switched from GCC to Clang for coverage (more accurate AST-based measurement). CI speed was a recurring concern: we skipped remote documentation generation on PRs, sped up release demos, and skipped long tests that were not catching new bugs. LLVM cache keys were unified to avoid redundant builds, and CTest timeouts were increased for sanitizer jobs that run significantly slower. Matheus Izvekov contributed the Clang coverage switch, fixed an infinite recursion in extraction, and moved the project to use system libs by default. CI infrastructure commits ed6b3bc ci: add codecov configuration 5426a0a ci: use clang for coverage d629173 fix(ci): unify redundant LLVM cache keys 36a3b51 ci: update actions to v1.9.1 7b2103a ci: increase CTest timeout for MSan jobs 086becc ci: increase the ctest timeout to 9000 adb6821 ci(cpp-matrix): remove the optimized-debug factor 9507a38 ci: simplify CI workflow and upgrade cpp-actions to @develop 9a5bd3c ci: skip remote documentation generation on PRs 637011f ci: detect and report demo generation failures 084322d ci: speed up release demos on PRs 471951d ci: skip long tests to speed up CI a5f160b ci: increase test coverage for the new XML generator b1fc43c ci: exclude Reflection.hpp from coverage a1f9a82 ci: accept any g++-14 version c136a46 ci(website): preserve roadmap directory during deployment 4763d86 revert(ci): remove premature roadmap report step 3462996 ci: revert coverage changes 8b2c3e9 ci: align llvm-sanitizer-config with archive basename fdff573 ci: gitignore CI node_modules 757d446 fix(ci): update the fmt branch reference from master to main a3366b0 fix(ci): name rolling release packages after the branch Test Infrastructure MrDocs uses golden tests: the expected output for every test case is stored as a file, and the test runner compares the actual output against it. The most important change was adding multipage golden tests. Previously, all golden tests were single-page, but many bugs only manifested in multi-page output (cross-references between pages, navigation links, index generation). We were missing these entirely because we had no way to test them. We also added output normalization (so platform differences do not cause false failures) and regression categories so tests can be grouped and run selectively. A run_ci_with_act.py script lets contributors run the full CI pipeline locally using act. Test infrastructure commits bf78b1b test: support multipage golden tests d7ad1ce test: output normalization ccd7f71 test: check int tests results in ctest 681b0cd chore: assign categories to regression tests 9146125 test: cover additional paths in DocCommentFinalizer.cpp 8326417 test: run_ci_with_act.py script 5527e9c test: testClang_stdCxx default is C++26 0dfdb02 test: –bad is disabled by default Acknowledgments and Reflections Going into the wild changed MrDocs. The edge cases, the customization requests, and the integration feedback shaped the direction more than any internal roadmap could. Gennaro Prota drove the reflection integration that reduces maintenance burden across the entire codebase. Matheus Izvekov hardened CI with coverage, sanitizers, and warnings-as-errors, and migrated dependency management to the bootstrap script. Julio Estrada and Robert Beeston delivered the polished public face of MrDocs. Agustín Bergé contributed AST and metadata fixes including base member shadowing and alias SFINAE detection. Jean-Louis Leroy provided detailed feedback from Boost.OpenMethod that drove multiple improvements. The most requested feature we have not solved yet is macro support (#1127). Macros are expanded before parsing and do not appear in the AST. Supporting them would require preprocessor-level integration with Clang. The work ahead also includes Lua scripting, metadata transforms, and deeper reflection, all direct responses to what users told us they need. The biggest lesson from this period is that the problems worth solving are the ones users bring. We spent time on an unknowns framework to decide what to explore, but the most impactful work came from people who showed up with a broken demo page, a missing breadcrumb, or a duplicate ellipsis in their generated docs. The complete set of changes is available in the MrDocs repository.</summary></entry><entry><title type="html">Boost.URL: Audited, Constexpr, and Polished</title><link href="http://cppalliance.org/alan/2026/04/21/Alan.html" rel="alternate" type="text/html" title="Boost.URL: Audited, Constexpr, and Polished" /><published>2026-04-21T00:00:00+00:00</published><updated>2026-04-21T00:00:00+00:00</updated><id>http://cppalliance.org/alan/2026/04/21/Alan</id><content type="html" xml:base="http://cppalliance.org/alan/2026/04/21/Alan.html"><p>We had been putting off the <a href="https://github.com/boostorg/url">Boost.URL</a> security review for a while. There was always something more urgent. When the review finally happened, it confirmed what we hoped: the core parsing logic held up well. Around the same time, a constexpr feature request that we had been dismissing suddenly became a cross-library collaboration when other Boost maintainers started applying changes to their own libraries. And while working on <a href="https://github.com/cppalliance/beast2">Boost.Beast2</a> integration, we noticed friction in common URL operations that led us to clear a backlog of usability improvements.</p>
<!-- prettier-ignore -->
<ul id="markdown-toc">
<li><a href="#security-review" id="markdown-toc-security-review">Security Review</a> <ul>
<li><a href="#round-1-1207-findings-february-2-2026" id="markdown-toc-round-1-1207-findings-february-2-2026">Round 1: 1,207 Findings (February 2, 2026)</a></li>
<li><a href="#round-2-27-findings-february-17-2026" id="markdown-toc-round-2-27-findings-february-17-2026">Round 2: 27 Findings (February 17, 2026)</a></li>
<li><a href="#round-3-15-findings-april-2-2026" id="markdown-toc-round-3-15-findings-april-2-2026">Round 3: 15 Findings (April 2, 2026)</a></li>
</ul>
</li>
<li><a href="#compile-time-url-parsing" id="markdown-toc-compile-time-url-parsing">Compile-Time URL Parsing</a> <ul>
<li><a href="#the-conversation-that-changed-everything" id="markdown-toc-the-conversation-that-changed-everything">The Conversation That Changed Everything</a></li>
<li><a href="#error-handling-at-compile-time" id="markdown-toc-error-handling-at-compile-time">Error Handling at Compile Time</a></li>
<li><a href="#the--wmaybe-uninitialized-problem" id="markdown-toc-the--wmaybe-uninitialized-problem">The <code>-Wmaybe-uninitialized</code> Problem</a></li>
<li><a href="#the-shared-library-problem" id="markdown-toc-the-shared-library-problem">The Shared Library Problem</a></li>
<li><a href="#the-result" id="markdown-toc-the-result">The Result</a></li>
</ul>
</li>
<li><a href="#usability-improvements" id="markdown-toc-usability-improvements">Usability Improvements</a> <ul>
<li><a href="#convenience-functions" id="markdown-toc-convenience-functions">Convenience Functions</a></li>
<li><a href="#c20-integration" id="markdown-toc-c20-integration">C++20 Integration</a></li>
<li><a href="#performance" id="markdown-toc-performance">Performance</a></li>
</ul>
</li>
<li><a href="#acknowledgments-and-reflections" id="markdown-toc-acknowledgments-and-reflections">Acknowledgments and Reflections</a></li>
</ul>
<h1 id="security-review">Security Review</h1>
<p>The <a href="https://cppalliance.org/">C++ Alliance</a> arranges professional security audits for the libraries we maintain. The results for <a href="https://www.boost.org/doc/libs/release/libs/beast/doc/html/beast/quick_start/security_review_bishop_fox.html">Boost.Beast (2020)</a> and <a href="https://cppalliance.org/pdf/C%20Plus%20Plus%20Alliance%20-%20Boost%20JSON%20Security%20Assessment%202020%20-%20Assessment%20Report%20-%2020210317.pdf">Boost.JSON (2021)</a> are publicly available. For Boost.URL, we always had the plan but kept delaying because there was so much other work to do first. That delay turned out to be a good thing: we found and fixed issues ourselves first, so the reviewers could focus on the subtle problems.</p>
<p><a href="https://www.laurellye.com/">Laurel Lye Systems Engineering</a> conducted <strong>three rounds</strong> of assessment. Each finding was manually reviewed against the source code and categorized as a confirmed bug (fixed), a false positive, or a deliberate design choice. For every confirmed bug, we also proposed new test cases to prevent regressions.</p>
<h2 id="round-1-1207-findings-february-2-2026">Round 1: 1,207 Findings (February 2, 2026)</h2>
<p>The first assessment was the broadest. Of 1,207 findings, <strong>15 were confirmed bugs</strong> resulting in fix commits. The vast majority were false positives or by-design patterns:</p>
<table>
<thead>
<tr>
<th>Verdict</th>
<th>CRITICAL</th>
<th>HIGH</th>
<th>MEDIUM</th>
<th>LOW</th>
<th>INFO</th>
<th>Total</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>FIXED</strong></td>
<td>1</td>
<td>9</td>
<td>0</td>
<td>2</td>
<td>3</td>
<td><strong>15</strong></td>
</tr>
<tr>
<td><strong>FALSE POSITIVE</strong></td>
<td>3</td>
<td>47</td>
<td>46</td>
<td>186</td>
<td>110</td>
<td><strong>392</strong></td>
</tr>
<tr>
<td><strong>BY DESIGN</strong></td>
<td>0</td>
<td>129</td>
<td>445</td>
<td>170</td>
<td>56</td>
<td><strong>800</strong></td>
</tr>
<tr>
<td><strong>Total</strong></td>
<td><strong>4</strong></td>
<td><strong>185</strong></td>
<td><strong>491</strong></td>
<td><strong>358</strong></td>
<td><strong>169</strong></td>
<td><strong>1,207</strong></td>
</tr>
</tbody>
</table>
<p>The single <strong>CRITICAL</strong> fix was a loop condition in <code>url_base</code> that dereferenced <code>*it</code> before checking <code>it != end</code>. Three other CRITICAL findings were false positives: the audit flagged raw-pointer writes in the format engine, but these use a two-phase measure/format design that guarantees the buffer is pre-sized correctly.</p>
<p>Most false positives fell into recognizable themes:</p>
<ul>
<li><strong><code>BOOST_ASSERT</code> as sole bounds check</strong> (29 HIGH findings): internal <code>_unsafe</code> functions rely on preconditions validated by the public API. The <code>_unsafe</code> suffix signals the contract. This is the standard Boost/STL pattern (<code>std::vector::operator[]</code> vs <code>at()</code>).</li>
<li><strong>Non-owning view lifetime safety</strong> (27 HIGH findings): <code>string_view</code> and <code>url_view</code> types do not own their data. The audit flagged potential use-after-free, but lifetime management is the caller’s responsibility by design.</li>
<li><strong>Atomic reference counting</strong> (multiple findings across all rounds): the audit tool did not recognize the <code>#ifdef BOOST_URL_DISABLE_THREADS</code> guard that switches between <code>std::atomic&lt;std::size_t&gt;</code> and plain <code>std::size_t</code>.</li>
</ul>
<details>
<summary>Round 1 fix commits</summary>
<ul>
<li><a href="https://github.com/boostorg/url/commit/bcdc891">bcdc891</a> CRITICAL: url_base loop condition order</li>
<li><a href="https://github.com/boostorg/url/commit/ec15fce">ec15fce</a> HIGH: encode() UB pointer arithmetic for small buffers</li>
<li><a href="https://github.com/boostorg/url/commit/81fcb95">81fcb95</a> HIGH: LLONG_MIN negation UB in format</li>
<li><a href="https://github.com/boostorg/url/commit/42c8fe7">42c8fe7</a> HIGH: ci_less::operator() return type</li>
<li><a href="https://github.com/boostorg/url/commit/76279f5">76279f5</a> HIGH: incorrect noexcept in segments_base::front() and back()</li>
<li><a href="https://github.com/boostorg/url/commit/d4ae92d">d4ae92d</a> HIGH: recycled_ptr::get() nullptr when empty</li>
<li><a href="https://github.com/boostorg/url/commit/8d98fe6">8d98fe6</a> LOW: decode() noexcept on throwing template</li>
</ul>
</details>
<p>The proportion of false positives to confirmed bugs was large enough that we discussed a second round with Laurel Lye, where we shared the false positive categories we had identified so they could be more targeted.</p>
<h2 id="round-2-27-findings-february-17-2026">Round 2: 27 Findings (February 17, 2026)</h2>
<p>The second assessment was more targeted. The reviewers had learned from our Round 1 triage and produced fewer false positives:</p>
<table>
<thead>
<tr>
<th>Verdict</th>
<th>HIGH</th>
<th>MEDIUM</th>
<th>LOW</th>
<th>INFO</th>
<th>Total</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>FIXED</strong></td>
<td>7</td>
<td>3</td>
<td>1</td>
<td>1</td>
<td><strong>12</strong></td>
</tr>
<tr>
<td><strong>FALSE POSITIVE</strong></td>
<td>2</td>
<td>2</td>
<td>0</td>
<td>0</td>
<td><strong>4</strong></td>
</tr>
<tr>
<td><strong>BY DESIGN</strong></td>
<td>0</td>
<td>0</td>
<td>1</td>
<td>1</td>
<td><strong>2</strong></td>
</tr>
<tr>
<td><strong>ALREADY FIXED</strong></td>
<td>0</td>
<td>5</td>
<td>4</td>
<td>0</td>
<td><strong>9</strong></td>
</tr>
<tr>
<td><strong>Total</strong></td>
<td><strong>9</strong></td>
<td><strong>10</strong></td>
<td><strong>6</strong></td>
<td><strong>2</strong></td>
<td><strong>27</strong></td>
</tr>
</tbody>
</table>
<p>9 of the 27 findings had <strong>already been fixed</strong> in Round 1 commits. The new confirmed bugs included a heap overflow in format center-alignment padding (<code>lpad = w / 2</code> used total width instead of padding amount), an infinite loop in <code>decode_view::ends_with</code> with empty strings, and an OOB read in <code>ci_is_less</code> on mismatched-length strings.</p>
<p>Both rounds are tracked in <a href="https://github.com/boostorg/url/pull/982">PR #982</a>.</p>
<details>
<summary>Round 2 fix commits</summary>
<ul>
<li><a href="https://github.com/boostorg/url/commit/d06df88">d06df88</a> HIGH: format center-alignment padding</li>
<li><a href="https://github.com/boostorg/url/commit/4fe2438">4fe2438</a> HIGH: decode_view::ends_with with empty string</li>
<li><a href="https://github.com/boostorg/url/commit/f5727ed">f5727ed</a> HIGH: stale pattern n.path after colon-encoding</li>
<li><a href="https://github.com/boostorg/url/commit/d045d71">d045d71</a> HIGH: ci_is_less OOB read</li>
<li><a href="https://github.com/boostorg/url/commit/88efbae">88efbae</a> HIGH: recycled_ptr copy self-assignment</li>
<li><a href="https://github.com/boostorg/url/commit/fe4bdf6">fe4bdf6</a> MEDIUM: url move self-assignment</li>
<li><a href="https://github.com/boostorg/url/commit/ab5d812">ab5d812</a> MEDIUM: encode_one signed char right-shift</li>
<li><a href="https://github.com/boostorg/url/commit/b662a8f">b662a8f</a> MEDIUM: encode() noexcept on throwing template</li>
<li><a href="https://github.com/boostorg/url/commit/5bc52ed">5bc52ed</a> LOW: port_rule has_number for port zero at end of input</li>
<li><a href="https://github.com/boostorg/url/commit/9c9850f">9c9850f</a> INFO: ci_equal arguments by const reference</li>
<li><a href="https://github.com/boostorg/url/commit/4f466ce">4f466ce</a> test: public interface boundary and fuzz tests</li>
</ul>
</details>
<h2 id="round-3-15-findings-april-2-2026">Round 3: 15 Findings (April 2, 2026)</h2>
<p>The third round was the shortest and the most precise. Of 15 findings, <strong>4 were confirmed bugs</strong> and <strong>11 were false positives</strong>. No CRITICAL findings. The false positives were the same recurring themes (atomic refcounting, pre-validated format strings, preconditions guaranteed by callers).</p>
<table>
<thead>
<tr>
<th>Verdict</th>
<th>HIGH</th>
<th>MEDIUM</th>
<th>LOW</th>
<th>Total</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>FIXED</strong></td>
<td>0</td>
<td>1</td>
<td>3</td>
<td><strong>4</strong></td>
</tr>
<tr>
<td><strong>FALSE POSITIVE</strong></td>
<td>4</td>
<td>6</td>
<td>1</td>
<td><strong>11</strong></td>
</tr>
<tr>
<td><strong>Total</strong></td>
<td><strong>4</strong></td>
<td><strong>7</strong></td>
<td><strong>4</strong></td>
<td><strong>15</strong></td>
</tr>
</tbody>
</table>
<p>The confirmed bugs were more subtle: a decoded-length calculation error in <code>segments_iter_impl::decrement()</code> that only manifested during backward iteration over percent-encoded paths, two <a href="https://en.cppreference.com/w/cpp/language/noexcept_spec"><code>noexcept</code></a> specifications on functions that allocate <code>std::string</code> (which can throw <code>bad_alloc</code>), and a <code>memcpy</code> with null source when size is zero (undefined behavior per the C standard, even though it copies nothing).</p>
<p>This round is tracked in <a href="https://github.com/boostorg/url/pull/988">PR #988</a>.</p>
<details>
<summary>Round 3 fix commits</summary>
<ul>
<li><a href="https://github.com/boostorg/url/commit/3ca2d71">3ca2d71</a> MEDIUM: segments_iter_impl decoded-length in decrement()</li>
<li><a href="https://github.com/boostorg/url/commit/b1f6f8e">b1f6f8e</a> LOW: param noexcept on throwing constructor</li>
<li><a href="https://github.com/boostorg/url/commit/d42c748">d42c748</a> LOW: string_view_base noexcept on throwing operator std::string()</li>
<li><a href="https://github.com/boostorg/url/commit/f963383">f963383</a> LOW: url_view memcpy with null source when size is zero</li>
</ul>
</details>
<p>The progression from 1,207 findings to 27 to 15 shows the reviewers learning the peculiarities of our codebase. The ratio of false positives dropped with each round, and the confirmed bugs got more subtle.</p>
<div class="mermaid">
%%{init: {"theme": "base", "themeVariables": {"primaryColor": "#e4eee8", "primaryBorderColor": "#affbd6", "primaryTextColor": "#000000", "lineColor": "#baf9d9", "secondaryColor": "#f0eae4", "tertiaryColor": "#ebeaf4", "fontSize": "14px"}}}%%
mindmap
root((Confirmed Bugs))
UB in edge cases
encode_one right-shift
LLONG_MIN negation
pointer arithmetic
Self-assignment
url move
recycled_ptr copy
OOB reads
ci_is_less
decode_view ends_with
Incorrect noexcept
encode / decode
segments_base front/back
param constructor
string_view_base operator
Iterator bugs
segments decoded-length
Null pointer
recycled_ptr get
url_view memcpy
</div>
<h1 id="compile-time-url-parsing">Compile-Time URL Parsing</h1>
<p><a href="https://github.com/boostorg/url/issues/890"><code>constexpr</code> URL parsing</a> has been one of the most recurring requests since the library’s inception. Every few months someone would ask about it, and every few months we would decide the refactoring cost was too high. The parsing engine is heavily buffer-oriented, and moving enough code into headers for <a href="https://en.cppreference.com/w/cpp/language/constexpr"><code>constexpr</code></a> evaluation required careful refactoring without breaking the shared library build.</p>
<p>When we finally prototyped it, the diff touched thousands of lines, but most of those were <strong>code being moved from source files to headers</strong> rather than new logic. The actual new code was limited to alternative code paths to bypass non-literal types and refactoring <code>url_view_base</code> to eliminate a self-referencing pointer that prevented <code>constexpr</code> evaluation. Still, given the size of the change, we initially marked it as unactionable and moved on to the security review.</p>
<p>Beyond the refactoring cost, we had <strong>blockers beyond our control</strong>. Our parsing code depended on <a href="https://github.com/boostorg/optional/issues/143"><code>boost::optional</code></a> (not a literal type, no constexpr constructors), <a href="https://github.com/boostorg/variant2"><code>boost::variant2</code></a> (not literal when containing <code>optional</code>), and <a href="https://github.com/boostorg/system/issues/141"><code>boost::system::result</code></a> (could not be constructed with a custom <code>error_code</code> in constexpr because <a href="https://www.boost.org/doc/libs/release/libs/system/doc/html/system.html#ref_error_category"><code>error_category::failed()</code></a> is virtual). Without changes to those libraries, constexpr URL parsing was not possible regardless of how much we refactored our own code.</p>
<h2 id="the-conversation-that-changed-everything">The Conversation That Changed Everything</h2>
<p>Then <a href="https://github.com/pdimov">Peter Dimov</a>, the maintainer of <a href="https://github.com/boostorg/system">Boost.System</a> and <a href="https://github.com/boostorg/variant2">Boost.Variant2</a>, joined the <a href="https://github.com/boostorg/url/issues/890">conversation</a>. We had assumed that <code>system::result&lt;T&gt;</code> could not be <code>constexpr</code> in C++14 because it wraps <code>error_code</code>, which uses virtual functions. Peter <a href="https://github.com/boostorg/url/issues/890#issuecomment-2720949684">pointed out</a> that <strong><code>system::result&lt;T&gt;</code> is already a literal type</strong> in C++14 when <code>T</code> is literal and the error code is not custom. Boost.URL uses a <strong>custom error code category</strong>, and constructing a <code>result</code> from a custom <code>error_code</code> requires calling <code>error_category::failed()</code>, which is virtual and therefore not <code>constexpr</code> before C++20. Peter <a href="https://github.com/boostorg/url/issues/890#issuecomment-3869061934">offered to fix this</a> in Boost.System (<a href="https://github.com/boostorg/system/issues/141">#141</a>, <a href="https://github.com/boostorg/system/commit/af53f17">af53f17</a>) for C++20 so that custom error codes would also work at compile time.</p>
<div class="admonition"><div class="admonition-title">Allowing constexpr virtual functions in C++20</div>
<p>Peter Dimov is also one of the authors of <a href="https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1064r0.html">P1064: “Allowing Virtual Function Calls in Constant Expressions”</a>, the C++ committee proposal that made <code>constexpr</code> virtual functions possible in C++20. The paper uses <code>error_code</code> and <code>error_category</code> as the motivating example.</p>
</div>
<p>That <strong>shifted the problem</strong>. Instead of building our own <code>constexpr_result&lt;T&gt;</code> type to bypass the entire error handling system, we could use <code>system::result</code> directly in C++20. The scope of the refactoring shrank, and we focused on <strong>C++20 as the initial target</strong>. The remaining blocker was that <code>system::result&lt;T&gt;</code> requires <code>T</code> to be a literal type, and we use <code>boost::optional</code> heavily in our parsing code. <strong><code>boost::optional</code> was not a literal type.</strong></p>
<p><a href="https://github.com/akrzemi1">Andrzej Krzemieński</a>, the Boost.Optional maintainer, <a href="https://github.com/boostorg/optional/issues/143">started working on it</a>. The conversation went back and forth on the <strong>C++14 constraints</strong>: <code>std::addressof</code> is not <code>constexpr</code> until C++17, mandatory copy elision is only available in C++17, and there were questions about what subset of constructors could realistically become <code>constexpr</code> in C++14. After several iterations (including a <code>feature/constexpr</code> branch), the <a href="https://github.com/boostorg/optional/commit/3df2337"><strong>constexpr implementation</strong> landed on <code>develop</code></a>.</p>
<p>With <strong><code>optional</code> becoming literal</strong>, <code>boost::variant2</code> containing <code>optional</code> could also become literal. All three blockers were now resolved. Peter had fixed Boost.System, Andrzej had fixed Boost.Optional, and we contributed fixes to Boost.Variant2. <strong>There was no going back</strong>: we could no longer dismiss the constexpr feature after three library maintainers had already done their part.</p>
<div class="mermaid">
%%{init: {"theme": "base", "themeVariables": {"primaryColor": "#f7f9ff", "primaryBorderColor": "#9aa7e8", "primaryTextColor": "#1f2a44", "lineColor": "#b4bef2", "secondaryColor": "#fbf8ff", "tertiaryColor": "#ffffff", "fontSize": "14px"}}}%%
flowchart TD
A[Boost.URL constexpr parsing] --&gt; B[Boost.Optional]
A --&gt; C[Boost.Variant2]
A --&gt; D[Boost.System]
B --&gt; E[boost::optional constexpr]
C --&gt; F[boost::variant2::variant constexpr]
D --&gt; G[boost::system::result constexpr]
D --&gt; H[boost::system::error_code constexpr]
</div>
<details>
<summary>Cross-library commits for constexpr support</summary>
<p><strong>Boost.URL</strong> (<a href="https://github.com/boostorg/url/pull/976">PR #976</a>, <a href="https://github.com/boostorg/url/pull/981">PR #981</a>)</p>
<ul>
<li><a href="https://github.com/boostorg/url/commit/0a2c39f">0a2c39f</a> feat: constexpr URL parsing for C++20</li>
<li><a href="https://github.com/boostorg/url/commit/b9db439">b9db439</a> build: remove -Wno-maybe-uninitialized from GCC flags (see <a href="#the--wmaybe-uninitialized-problem">below</a>)</li>
<li><a href="https://github.com/boostorg/url/commit/59b4540">59b4540</a> fix: suppress GCC false-positive -Wmaybe-uninitialized in tuple_rule (see <a href="#the--wmaybe-uninitialized-problem">below</a>)</li>
</ul>
<p><strong>Boost.Optional</strong> (<a href="https://github.com/boostorg/optional/issues/143">issue #143</a>, <a href="https://github.com/boostorg/optional/pull/145">PR #145</a>)</p>
<ul>
<li><a href="https://github.com/boostorg/optional/commit/3df2337">3df2337</a> make optional constexpr in C++14</li>
<li><a href="https://github.com/boostorg/optional/commit/046357c">046357c</a> add more robust constexpr support</li>
<li><a href="https://github.com/boostorg/optional/commit/88e2378">88e2378</a> add -Wmaybe-uninitialized pragma (see <a href="#the--wmaybe-uninitialized-problem">below</a>)</li>
</ul>
<p><strong>Boost.Variant2</strong> (<a href="https://github.com/boostorg/variant2/pull/57">PR #57</a>)</p>
<ul>
<li><a href="https://github.com/boostorg/variant2/commit/b6ce8ac">b6ce8ac</a> add missing -Wmaybe-uninitialized pragma (see <a href="#the--wmaybe-uninitialized-problem">below</a>)</li>
</ul>
<p><strong>Boost.System</strong> (<a href="https://github.com/boostorg/system/issues/141">issue #141</a>)</p>
<ul>
<li><a href="https://github.com/boostorg/system/commit/af53f17">af53f17</a> add constexpr to virtual functions on C++20 or later</li>
</ul>
</details>
<h2 id="error-handling-at-compile-time">Error Handling at Compile Time</h2>
<p>Boost.URL attaches <a href="https://en.cppreference.com/w/cpp/utility/source_location">source location</a> information to error codes for better diagnostics at runtime. In a <code>constexpr</code> context, <code>BOOST_CURRENT_LOCATION</code> is not available, so the <code>BOOST_URL_CONSTEXPR_RETURN_EC</code> macro branches on <a href="https://en.cppreference.com/w/cpp/types/is_constant_evaluated"><code>__builtin_is_constant_evaluated()</code></a>: at compile time it returns the error enum directly, at runtime it attaches the source location.</p>
<pre><code class="language-cpp">#if defined(BOOST_URL_HAS_CXX20_CONSTEXPR)
# define BOOST_URL_CONSTEXPR_RETURN_EC(ev) \
do { \
if (__builtin_is_constant_evaluated()) { \
return (ev); \
} \
return [](auto e) { \
BOOST_URL_RETURN_EC(e); \
}(ev); \
} while(0)
#endif
</code></pre>
<h2 id="the--wmaybe-uninitialized-problem">The <code>-Wmaybe-uninitialized</code> Problem</h2>
<p>GCC’s <a href="https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html#index-Wmaybe-uninitialized"><code>-Wmaybe-uninitialized</code></a> flagged code inside <code>boost::optional</code> and <code>boost::variant2</code> union storage constructors. The root cause was neither library.</p>
<p><strong>The inlining chain:</strong> Boost.URL’s parsing code constructs a <code>variant2::variant</code> that contains an <code>optional</code> alternative. At <strong><code>-O3</code></strong>, GCC inlines the entire chain:</p>
<ul>
<li>Parse function</li>
<li>Variant construction</li>
<li><code>variant2</code> storage</li>
<li><code>optional</code> storage</li>
<li>Union constructor</li>
</ul>
<p>After inlining, GCC sees a union with a <code>dummy_</code> member and a <code>value_</code> member, and it cannot prove which member is active. It conflates the “uninitialized dummy” path with the “initialized value” path. The <code>in_place_index_t&lt;I&gt;</code> dispatch guarantees which member is initialized, but GCC’s data flow analysis loses track across the nested layers. <a href="https://clang.llvm.org/docs/AddressSanitizer.html"><code>-fsanitize=address</code></a> makes it worse by changing inlining thresholds.</p>
<p><strong>The compiler blames the wrong library.</strong> The root cause is in <code>variant2</code>’s union storage, but when <code>variant2</code> contains an <code>optional</code>, GCC reports the warning in <code>optional</code>’s code. The pragma has to go where GCC reports it, not where the issue originates. We contributed pragmas to both <a href="https://github.com/boostorg/optional/pull/145">Boost.Optional</a> and <a href="https://github.com/boostorg/variant2/pull/57">Boost.Variant2</a>, and replaced Boost.URL’s blanket <code>-Wno-maybe-uninitialized</code> flag with <a href="https://github.com/boostorg/url/pull/981">targeted pragmas</a>.</p>
<blockquote>
<p>This particular false positive requires <strong>GCC 14+</strong>, <strong><code>-O3</code></strong>, <strong>ASan</strong>, on <strong>x86_64 Linux</strong>, with a <code>variant2::variant</code> containing a <code>boost::optional</code>, constructed through a <code>system::result</code> dereference. Change any one of those conditions and the warning disappears.</p>
</blockquote>
<p>This leaves an open question for the Boost ecosystem: when a false positive surfaces because library A’s optimizer behavior interacts with library B’s union storage and gets reported in library C’s code, who is responsible for the pragma? For now, we placed pragmas where GCC reports the issue, but the underlying problem recurs every time a new combination of types triggers the same inlining pattern.</p>
<h2 id="the-shared-library-problem">The Shared Library Problem</h2>
<p>Making URL parsing <code>constexpr</code> means the parsing functions must be available in headers. But Boost.URL is a compiled library, and on MSVC, <a href="https://learn.microsoft.com/en-us/cpp/cpp/dllexport-dllimport?view=msvc-170"><code>__declspec(dllexport)</code></a> on a class exports <strong>all</strong> members, including inline and <code>constexpr</code> ones. This causes <a href="https://learn.microsoft.com/en-us/cpp/error-messages/tool-errors/linker-tools-error-lnk2005?view=msvc-170"><code>LNK2005</code></a> (duplicate symbol) errors for any class that mixes compiled and header-only members.</p>
<p>Each class must follow exactly one of two policies:</p>
<ul>
<li><strong>(a)</strong> Fully compiled: <code>class BOOST_URL_DECL C</code>. All members in <code>.cpp</code> files. No inline or <code>constexpr</code> members.</li>
<li><strong>(b)</strong> Fully header-only: <code>class BOOST_SYMBOL_VISIBLE C</code>. All inline/<code>constexpr</code>/template. No <code>.cpp</code> file.</li>
</ul>
<p>We documented the full rationale in <a href="https://github.com/boostorg/url/blob/develop/include/boost/url/detail/config.hpp"><code>config.hpp</code></a>. We suspect other C++ libraries have not encountered this because they either do not test shared library builds as extensively as we do, or they are header-only.</p>
<h2 id="the-result">The Result</h2>
<p>Boost.URL can now parse URLs at compile time under C++20 (<a href="https://github.com/boostorg/url/pull/976">PR #976</a>). All parse functions (<code>parse_uri</code>, <code>parse_uri_reference</code>, <code>parse_relative_ref</code>, <code>parse_absolute_uri</code>, and <code>parse_origin_form</code>) are fully <code>constexpr</code>. A malformed URL literal becomes a compile error rather than a runtime failure:</p>
<pre><code class="language-cpp">// Parsed and validated at compile time.
// A malformed literal would fail to compile.
constexpr url_view api_base =
parse_uri("https://api.example.com/v2").value();
</code></pre>
<p>Pre-parsed <code>constexpr</code> URL views also serve as <strong>zero-cost constants</strong>: because all parsing happens during compilation, components like scheme, host, and port are available at runtime with no parsing overhead. This is useful for applications that compare against well-known endpoints, pre-populate configuration defaults, or build routing tables without paying for string parsing at startup.</p>
<p>The constexpr feature taught us that dismissing a request because the cost seems too high for one library misses the bigger picture. Once Peter Dimov and the other maintainers got involved, the cost was shared and the scope shrank. In the Boost ecosystem, a feature that seems expensive in isolation can become practical when the dependencies cooperate.</p>
<h1 id="usability-improvements">Usability Improvements</h1>
<p>While integrating Boost.URL into <a href="https://github.com/cppalliance/beast2">Boost.Beast2</a>, the Beast2 authors noticed friction in common operations that worked correctly but required more code than they should. At the same time, several community issues had been open for a while. We used this as an opportunity to address both.</p>
<h2 id="convenience-functions">Convenience Functions</h2>
<p>The most requested feature was <strong><a href="https://github.com/boostorg/url/pull/953"><code>get_or</code></a></strong> for query containers: look up a query parameter by key and return a default value if it is not present.</p>
<p><strong>Before:</strong></p>
<pre><code class="language-cpp">auto it = url.params().find("page");
auto page = it != url.params().end() ? (*it).value : "1";
</code></pre>
<p><strong>After:</strong></p>
<pre><code class="language-cpp">auto page = url.params().get_or("page", "1");
</code></pre>
<p>We also added <strong><a href="https://github.com/boostorg/url/pull/952">standalone decode functions</a></strong> for working with individual URL components without constructing a full URL object:</p>
<pre><code class="language-cpp">auto plain = decode("My%20Stuff");
assert(plain &amp;&amp; *plain == "My Stuff");
auto n = decoded_size("Program%20Files");
assert(n &amp;&amp; *n == 13);
</code></pre>
<h2 id="c20-integration">C++20 Integration</h2>
<p><a href="https://github.com/boostorg/url/pull/966"><code>enable_borrowed_range</code></a> is now specialized for 10 Boost.URL view types (<code>segments_view</code>, <code>params_view</code>, <code>decode_view</code>, and others). Unlike a <code>std::vector</code>, which owns its data, Boost.URL views point into the URL’s buffer without owning it. When a temporary view is destroyed, its iterators still point to valid memory. <a href="https://en.cppreference.com/w/cpp/ranges/borrowed_range"><code>enable_borrowed_range</code></a> tells the compiler this is safe, so algorithms like <a href="https://en.cppreference.com/w/cpp/algorithm/ranges/find"><code>std::ranges::find</code></a> can return iterators from temporary views without the compiler rejecting the code:</p>
<pre><code class="language-cpp">segments_view::iterator it;
{
segments_view ps("/path/to/file.txt");
it = ps.begin();
}
// iterator is still valid (points to external buffer)
assert(*it == "path");
</code></pre>
<p>The grammar system gained <strong><a href="https://github.com/boostorg/url/pull/950">user-provided RangeRule support</a></strong>. Custom grammar rules for parsing URL components satisfy a concept requiring <code>first()</code> and <code>next()</code> methods returning <code>system::result&lt;value_type&gt;</code>:</p>
<pre><code class="language-cpp">struct my_range_rule
{
using value_type = core::string_view;
system::result&lt;value_type&gt;
first(char const*&amp; it, char const* end) const noexcept;
system::result&lt;value_type&gt;
next(char const*&amp; it, char const* end) const noexcept;
};
</code></pre>