-
Notifications
You must be signed in to change notification settings - Fork 0
/
throws_nothing_policy.bs
1490 lines (1199 loc) · 76.4 KB
/
throws_nothing_policy.bs
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
<pre class='metadata'>
Title: `noexcept` policy for SD-9 (throws nothing)
Shortname: P3085
Revision: 2
Audience: LEWG
Status: P
Group: WG21
URL: https://wg21.link/P3085
!Source: <a href="https://github.com/ben-craig/freestanding_proposal/blob/master/library/throws_nothing_policy.bs">github.com/ben-craig/freestanding_proposal/blob/master/library/throws_nothing_policy.bs</a>
Editor: Ben Craig, ben.craig@gmail.com
Abstract:
Markup Shorthands: markdown yes
</pre>
Revision history {#rev}
=====================
R2 {#rev-r2}
----
* Include worked principled design example from the Tokyo 2024 WG21 meeting in [[#appendix_principled_design]].
* Updated design choices to include Minimal `noexcept` policy in [[#wording_minimal_noexcept]].
* Discuss `noexcept`s affects on vectorization in [[#vectorization]].
* Discuss not throwing away useful info in [[#dont_discard_useful_info]].
* Discuss [[Baker3166]] in [[#other_impls]].
* Discuss [[Ažman3205]] in [[#contracts_change_noexcept]].
* Discuss [[Gustafsson3183]] in [[#reflection_for_contracts]]
* Discuss making `noexcept` entirely QoI, similar to [[Wakely3201]], in [[#noexcept_as_qoi]].
* Discuss DejaGnu in [[#death_tests]].
* Discuss "Can't write it better yourself" in [[#size_bloat]]
R1 {#rev-r1}
----
* Added principled design and notable omissions sections.
* Removed reference to undefined term "pass-through" function.
R0 {#rev-r0}
----
First revision!
Introduction {#intro}
=====================
The first priority of this paper is to establish a lasting policy on the usage of `noexcept` in the standard library that avoids re-litigation for a few years.
The second priority is to establish the author's preferred policy.
This paper currently recommends the policy that functions that LEWG and LWG agree cannot throw should be marked unconditionally `noexcept`.
This paper discusses alternative policies as well, including the old "Lakos rule", and a newer "minimal noexcept" rule.
With luck, one of these policies can gain consensus.
Prior `noexcept` Discussions {#prior}
===========
History papers {#history_papers}
------------
* [N2855, Rvalue References and Exception Safety](https://www.open-std.org/JTC1/SC22/WG21/docs/papers/2009/n2855.html), [[GregorN2855]]
* [N2983, Allowing Move Constructors to Throw](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2009/n2983.html), [[AbrahamsN2983]]
* [N3248, noexcept Prevents Library Validation.](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3248.pdf), [[MeredithN3248]]
* [N3279, Conservative use of noexcept in the Library](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3279.pdf), [[MeredithN3279]]
* [P0884R0, Extending the noexcept Policy, Rev0](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0884r0.pdf), [[Josuttis0884]]
* [P2920R0, Library Evolution Leadership's Understanding of the Noexcept Policy History](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2920r0.pdf), [[Lelbach2920]]
Throws Nothing Rule {#throws_nothing_papers}
------------
* [P1656R2, "Throws: Nothing" should be noexcept](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p1656r2.html), [[Bergé1656]]
* [P2148R0, Library Evolution Design Guidelines](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p2148r0.pdf), [[Johnson2148]]
Narrow `noexcept` / Lakos Rule {#narrow_noexcept_papers}
------------
* [P2831R0, Functions having a narrow contract should not be noexcept](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2831r0.pdf), [[Doumler2831]]
* [P2837R0, Planning to Revisit the Lakos Rule](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2837r0.pdf), [[Meredith2837]]
* [P2861R0, The Lakos Rule: Narrow Contracts And noexcept Are Inherently Incompatible](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2861r0.pdf), [[Lakos2861]]
* [P2949R0, Slides for P2861R0: Narrow Contracts and noexcept are Inherently Incompatable](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2949r0.pdf), [[Lakos2949]]
* [P2946R0, A flexible solution to the problems of noexcept](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2946r0.pdf), [[Halpern2946]]
Both {#combined_papers}
-----
* [P2858R0, Noexcept vs contract violations](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2858r0.html), [[Krzemieński2858]]
* This paper, P3085, `noexcept` policy for SD-9 (throws nothing)
* [P3005R0, Memorializing Principled-Design Policies for WG21](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p3005r0.pdf), [[Lakos3005]]
Policies in question {#wording}
===============
Throws Nothing Rule (proposed) {#wording_throws_nothing}
-----
This wording does not match [[Bergé1656]], but it has the same intent.
The changed wording from [[Bergé1656]] is as follows:
> b. Each library function that LEWG and LWG agree cannot throw, should be marked as unconditionally `noexcept`.
The [[Bergé1656]] wording could be interpreted as disallowing narrow contract functions, since a function that is called without meeting preconditions could do anything, including throw.
That interpretation isn't the intent of [[Bergé1656]].
Instructions to the editor: Please add the following to the "List of Standard Library Policies" in SD-9.
<blockquote>
### `noexcept` policy {#wording_throws_nothing_heading}
Rationale reference: <a href="https://wg21.link/P3085">P3085</a>
<ol type="a">
<li>No library destructor should throw. They shall use the implicitly supplied (non-throwing) exception specification.</li>
<li><ins>Each library function that will not throw when called with all preconditions satisfied should be marked as unconditionally `noexcept`.</ins></li>
<li>If a library swap function, move-constructor, or move-assignment operator is conditionally-wide (i.e. can be proven to not throw by applying the `noexcept` operator) then it should be marked as conditionally `noexcept`.</li>
<li>If a library type has wrapping semantics to transparently provide the same behavior as the underlying type, then default constructor, copy constructor, and copy-assignment operator should be marked as conditionally `noexcept` the underlying exception specification still holds.</li>
<li>No other function should use a conditional `noexcept` specification.</li>
<li>Library functions designed for compatibility with “C” code (such as the atomics facility), may be marked as unconditionally `noexcept`.</li>
</ol>
</blockquote>
Narrow `noexcept` / Lakos Rule (not proposed in this revision) {#wording_lakos}
-----
This wording matches the wording in [[Josuttis0884]].
<blockquote>
### `noexcept` policy {#wording_lakos_heading}
Rationale reference: <a href="https://wg21.link/P3085">P3085</a>
<ol type="a">
<li>No library destructor should throw. They shall use the implicitly supplied (non-throwing) exception specification.</li>
<li><ins>Each library function having a <em>wide</em> contract (i.e., does not specify undefined behavior due to a precondition) that the LWG agree cannot throw, should be marked as unconditionally `noexcept`.</ins></li>
<li>If a library swap function, move-constructor, or move-assignment operator is conditionally-wide (i.e. can be proven to not throw by applying the `noexcept` operator) then it should be marked as conditionally `noexcept`.</li>
<li>If a library type has wrapping semantics to transparently provide the same behavior as the underlying type, then default constructor, copy constructor, and copy-assignment operator should be marked as conditionally `noexcept` the underlying exception specification still holds.</li>
<li>No other function should use a conditional `noexcept` specification.</li>
<li>Library functions designed for compatibility with “C” code (such as the atomics facility), may be marked as unconditionally `noexcept`.</li>
</ol>
</blockquote>
Minimal `noexcept` {#wording_minimal_noexcept}
-----
Note: this policy is intentionally only two items long.
Items c. through f. are intentionally not present.
<blockquote>
### `noexcept` policy {#wording_minimal_heading}
Rationale reference: <a href="https://wg21.link/P3085">P3085</a>
<ol type="a">
<li>No library destructor should throw. They shall use the implicitly supplied (non-throwing) exception specification.</li>
<li><ins>If the result of the `noexcept` operator on a library function can conditionally be used to select a more efficient algorithm due to the exception safety requirements (e.g. move and swap operations), then the library function should be marked as conditionally noexcept.</ins></li>
</ol>
</blockquote>
Status Quo {#status_quo}
==========
Currently, there is no policy on `noexcept`.
Between 2011 and 2020, `noexcept` was applied to the standard library in line with the narrow `noexcept` rule [[MeredithN3279]] [[Josuttis0884]].
In 2020, the throws nothing rule was proposed [[Bergé1656]].
The designs that LEWG has forwarded to LWG between 2020 and the present day have been following the narrow `noexcept` rule.
There are 78 occurrences of "Throws: Nothing." in the standard [[Cpp2023-12]], but this number is misleading as many of the usages are in blanket wording that gets applied to multiple classes.
The Microsoft Visual Studio 2022 Preview implementation (version 14.38.32919) annotates each `noexcept` they strengthen with a `/* strengthened */` comment.
There are 633 hits of the "noexcept /* strengthened */" string in the headers for this implementation.
All of these are either definitions of functions, or macros that make stamping out `<cmath>` overload definitions easier.
In other words, these hits aren't counting both forward declarations plus definitions, they are only counting definitions.
This search omits any conditionally `noexcept` functions.
There are roughly 3,600 instances of the string "noexcept;" in the standard.
A few of these are in examples.
For most `noexcept` functions, "noexcept;" will show up twice in the standard [[Cpp2023-12]], once in a synopsis and once in the detailed specification.
This means there are approximately 1,800 unconditionally `noexcept` functions in the standard.
This search omits any conditionally `noexcept` functions.
Why should we establish any `noexcept` policy? {#why_bother}
==========
Core-language Contracts {#core_language_contracts}
-----
Interactions between `noexcept` and the upcoming core-language contracts facilities is likely to be significant.
It would be unfortunate if decisions in the library prevented the core-language contracts work from reaching its full potential.
[[Meredith2837]] argues that the interaction may be significant and unpredictable enough that it would be better to proceed with no policy in the short term, rather than settle on a policy that will immediately get in the way of contracts.
If we don't set a policy, then we leave it to paper authors and LEWG to determine the proper `noexcept` specifier on a per-function basis.
This is likely to cause a great deal of inconsistency in the interim.
Consistency over preference {#consistency_over_preference}
-----
There are some design areas where the difference in two choices is of little consequence.
LEWG will often spend some time rediscovering the trade-offs and existing practice around the differences, and then make a somewhat arbitrary decision.
In these cases, LEWG tends to bias towards "whatever the bulk of the standard is currently doing".
LEWG could become more self consistent by documenting one way, and then requiring authors to either follow the policy, or provide rationale for why the policy should not be applied in this case.
The explicit-ness of multi-argument constructors is one such area [[Voutilainen2711]].
Changes in direction {#changes_in_direction}
-----
Sometimes, LEWG follows one design pattern in the standard, then discovers or invents a new pattern.
The new pattern is inconsistent with the old pattern.
Authors then wonder whether to follow the new pattern or old pattern.
This is a reasonable place for LEWG to document a policy.
Using hidden `friend`s for non-member operators is one example of a change in direction.
Whether to make all new "free functions" in the standard library customization point objects (like `std::ranges`) is another.
Cross group trust {#cross_group_trust}
-----
There are a lot of discussions happening concurrently within WG21.
Sometimes, one group wants to rely on conventions from another group.
These policies help people focus on the groups they are needed, rather than spend a large portion of their time ensuring their policy is followed (or not) in LEWG.
An example policy here would be ensuring that `const` means either shallow bitwise const or internally synchronized.
The concurrency study group is likely relying on that convention to be followed, so changing `const` directions would cause surprise.
Why the LEWG `noexcept` policy isn't actually that important {#not_that_important}
==========
Implementations already strengthen the `noexcept` status of many STL functions.
MSVC STL, libc++, and libstdc++ all mark `vector::back()`, `vector::operator[]`, and many other functions as `noexcept`.
Marking existing functions in the standard `noexcept` will change the results of very few instances of the `noexcept` operator in practice.
If the committee continues to permit implementations to strengthen the `noexcept` status of functions, then implementations won't change.
If the committee chooses to remove this permission, implementations will either ignore the requirement and be non-conforming, or they will accept the code breakage that the backwards compatibility arguments [[#backwards_compat]] are trying to avoid.
If we are breaking compatibility in this way once, we may very well break it again in the future.
The main way changing the `noexcept` policy changes things for standardization and users is if we remove the permission to strengthen `noexcept` and implementations grudgingly take a one time (but not an N time) compatibility hit.
Note that this paper makes no attempt to change the `noexcept` strengthening rules in either it's proposed rules or the presented alternative.
Arguments {#arguments}
==========
This section is organized by topic, and not by policy.
That means that sections discuss one topic at a time (e.g. testing), and then discuss the affect of the policies on the topic.
Algorithmic optimizations and exception safety {#algorithmic_optimizations}
-----
[[GregorN2855]], [[AbrahamsN2983]], [[MeredithN3248]]
The original, primary motivation for `noexcept` was to make it possible for `vector` and similar constructs to take advantage of `move` optimizations, without breaking existing code (e.g. `vector`'s strong exception guarantee).
This motivation and use case is uncontested in the various papers debating the usage of `noexcept`, so this paper will not spend further time on it.
On most implementations, disabling exceptions does not change the result of the `noexcept` operator (The discontinued Intel ICC makes the `noexcept` operator return `true` unconditionally under `-fno-exceptions`).
This means that `noexcept` is still needed in order to opt-in to algorithmic optimizations, even when exceptions are disabled.
Narrow to wide substitutability / backwards compatibility {#backwards_compat}
-----
[[Doumler2831]], [[Lakos2949]], [[Lakos2861]]
Suppose we have a function `void unchecked_f(int x)` with a precondition of `x > 0`.
`unchecked_f` has undefined behavior when the precondition is violated.
Now suppose we have a function `void checked_f(int x)` that has the same behavior as `unchecked_f` when the precondition is met, but it throws an exception when the precondition is violated.
`checked_f` and `unchecked_f` are substitutable for each other, as all in-contract uses behave the same.
Now suppose we have a function `void noexcept_f(int x) noexcept` that has the same behavior as `unchecked_f` when the precondition is met.
`noexcept_f` and `unchecked_f` aren't substitutable with each other, due to the change in behavior of the `noexcept` operator.
`noexcept(noexcept_f(0))` returns `true`, and `noexcept(unchecked_f(0))` returns `false`.
This change in behavior constrains implementations and the C++ committee.
In theory, avoiding `noexcept` gives WG21 more latitude to make changes in the future.
In practice, this latitude is unneeded, and difficult to use.
The author is unaware of any previous proposal that attempted to make a "Throws: nothing" function released in one standard into a function that could throw something in a later standard.
This suggests that this freedom to change isn't that valuable in practice.
We've had the freedom, but haven't needed it or used it.
Using the freedom to make an out-of-contract call throw an exception would be a compatibility break in practice.
MSVC STL, libc++, and libstdc++ all mark `vector::back()`, `vector::operator[]`, and many other functions as `noexcept`.
If we were to require `vector::back()` to be `noexcept(false)` so that it could throw when the container is empty, it would cause a (minor) break for the majority of C++ code bases migrating to that standard due to the change in the `noexcept` operator's result.
Perhaps such a break would be small enough to gain consensus in WG21 though.
Major implementations don't currently use `noexcept` latitude to implement throwing precondition checks.
The three major implementations (MSVC STL, libstdc++, and libc++) all have checked implementations.
All three use some variation of termination as their mechanism of handling a failed check.
Less prevalent implementations do use such latitude for checked implementations and library testing.
Codegen changes {#codegen_changes}
-----
### "Modern" implementations ### {#modern_impls}
Most platforms use the "table-based" implementation strategy for exceptions.
The combinations of (x86-64, ARM, Aarch64) X (Windows, Linux, iOS, OSX) all use table-based exceptions.
With table-based implementations, `noexcept` changes the generated assembly.
There are sometimes extra stack manipulations, and minor bookkeeping changes, but no extra branches.
Those minor changes affect the performance in a small and measurable way, but sometimes that difference is positive (for `noexcept`), and sometimes negative (against `noexcept`).
The magnitude of the difference is under six cycles for the programs and compilers tested [[Craig1886]].
Full application testing might show some instruction cache effects, but it is even more likely that any differences would be lost in the noise and immeasurable.
### Other implementations ### {#other_impls}
[[Doumler2831]]
Not all implementations use the table-based implementation strategy.
These implementations have more substantial bookkeeping relative to table-based implementations.
The most well known of these implementations is the Microsoft Windows 32-bit x86 implementation.
Each new cleanup context requires manipulating a linked list of cleanup actions, even on the happy path.
This results in substantial extra code, and more than a 20% execution performance penalty with micro-benchmarks relative to exceptions being disabled [[Craig1886]].
With heavy use of `noexcept`, the overhead can be removed.
The default and recommended exception project settings of the Microsoft Visual Studio compilers is `/EHsc`, which (non-conformingly) makes `extern "C"` functions `noexcept` by default as a way to reduce exception overhead.
While the days of peak Microsoft Windows for 32-bit x86 are far behind us, new compilers targeting the platform are still regularly released and downloaded.
Microsoft Visual Studio 2022 (the latest version at time of writing) still ships compilers targeting 32-bit Windows.
Clang 17.0.5 (released on Nov 14, 2023) still targets 32-bit Windows.
Download statistics for Clang binaries are available through the GitHub API.
```
// Clang 17.0.5 download statistices, gathered on Feb 2, 2024
// https://api.github.com/repos/llvm/llvm-project/releases
LLVM-17.0.5-win32.exe : 1422 downloads
LLVM-17.0.5-win64.exe : 22370 downloads
LLVM-17.0.5-woa64.exe : 304 downloads
clang+llvm-17.0.5-aarch64-linux-gnu.tar.xz : 532 downloads
clang+llvm-17.0.5-arm64-apple-darwin22.0.tar.xz : 585 downloads
clang+llvm-17.0.5-x86_64…gnu-ubuntu-22.04.tar.xz : 1803 downloads
clang+llvm-17.0.5-powerpc64-ibm-aix-7.2.tar.xz : 69 downloads
```
Readers should not try to make too many inferences about the relative popularity of platforms based on these numbers, as developers can get compiler releases from multiple sources that wouldn't register on the GitHub download page.
Regardless, the Microsoft Windows 32-bit platform still gets used, even with the most recent compilers, so it should not be brushed off as irrelevant.
James Renwick has an alternative implementation of exceptions [[Renwick2019]] with the intent of improving determinism in embedded systems.
This implementation also needs to manipulate bookkeeping information in each new cleanup context.
The bookkeeping information is passed along to potentially throwing functions through a hidden parameter.
`noexcept` can remove the cost of the hidden parameter, but can introduce a new cleanup context.
[[Baker3166]] proposes "Static Exception Specifications".
This proposal aims to make exceptional code path execution times similar in speed to non-exceptional code paths.
The proposal would also statically check that the exception specifications of callees is compatible with the caller's exception specification.
This would place a heightened need for more `noexcept` functions in the standard library, both for well-formedness reasons and for performance reasons.
On GPUs, there is a large cost to having additional control flow edges.
Using `noexcept` can remove control flow edges.
However, to the author's knowledge, no GPU currently supports C++ exceptions.
### Bloat ### {#size_bloat}
[[Bergé1656]], [[Doumler2831]], [[Lakos2861]]
The "tables" backing table-based exception handling, and the code implementing non-table-based bookkeeping all consume space.
Adding `noexcept` can reduce code bloat, as this can reduce the amount of information that needs to go into tables or bookkeeping.
Adding `noexcept` can also cause bloat if the `noexcept` function calls potentially throwing functions, as this transformation may need to introduce table entries or bookkeeping to ensure that `std::terminate` gets called.
In Compiler Explorer, bloat is best examined by disabling debug information (`-g0` for Clang and GCC), enabling optimizations (`-O2`), and leaving directives in the output (uncheck the box in "Filter...").
As a (very rough) estimate of bloat, we can use lines of assembly output as a proxy.
Some of the lines don't end up as bytes in the resulting binary, but many do show up, often as variable length integers.
Measuring the results on actual binaries is substantially more difficult, due to section alignment quantizing and obfuscating size differences.
[[Craig1640]] presents size benchmarks comparing exceptions to abort, and some of these measurements can be used as proxy byte-based statistics for `noexcept` bloat.
[This code](https://godbolt.org/z/MoGMh9Gxo) makes it possible to see what happens to the quantity of assembly when switching whether `caller` is `noexcept` or not, and whether `callee` is `noexcept` or not. All measurements are in the very rough lines of assembly measurement.
<table>
<tr>
<th>Compiler</th>
<th>`noexcept(false)` calls `noexcept(false)`</th>
<th>`noexcept(false)` calls `noexcept(true)`</th>
<th>`noexcept(true)` calls `noexcept(false)`</th>
<th>`noexcept(true)` calls `noexcept(true)`</th>
</tr>
<tr>
<td>x86 msvc v19.38</td>
<td>76</td>
<td>25</td>
<td>70</td>
<td>25</td>
</tr>
<tr>
<td>x64 msvc v19.38</td>
<td>44</td>
<td>26</td>
<td>37</td>
<td>26</td>
</tr>
<tr>
<td>x86-64 gcc 13.2</td>
<td>85</td>
<td>20</td>
<td>32</td>
<td>20</td>
</tr>
<tr>
<td>x86-64 clang 17.0.1</td>
<td>68</td>
<td>22</td>
<td>76</td>
<td>22</td>
</tr>
<tr>
<td>ARM64 gcc 13.2.0</td>
<td>69</td>
<td>26</td>
<td>46</td>
<td>26</td>
</tr>
<tr>
<td>armv8-a clang 17.0.1</td>
<td>74</td>
<td>30</td>
<td>88</td>
<td>30</td>
</tr>
</table>
For many platforms and applications, this bloat is inconsequential.
For many platforms and applications that are size constrained, exceptions are already disabled.
When exceptions are disabled, the "`noexcept(true)` calls `noexcept(true)`" code and the exception disabled code take the same number of lines of assembly.
If standard library "Throws: nothing" functions are left as `noexcept(false)`, then users can write those functions better themselves in terms of code bloat.
This demonstrates a violation of the zero-overhead rule [[StroustrupDE]].
### Vectorization ### {#vectorization}
`noexcept(false)` functions have an additional control flow edge compared to `noexcept(true)` functions.
In theory, this additional control flow edge could impact optimizations like vectorization.
The x86-64 versions of Clang 18.1.0, GCC 13.2, and ICX 2024.0.0 are not able to vectorize around opaque function calls.
The optimizer can ignore the control flow edge when called function bodies are visible to the compiler, and can often optimize accordingly.
As of the writing of this paper, the author does not have any examples where changing `noexcept` on a function changes vectorization decisions.
Library Testing {#library_testing}
-----
[[MeredithN3248]], [[Doumler2831]]
Implementations often constrain some undefined behavior for out-of-contract function invocations, particularly in debug and checked modes.
"Negative testing" validates that the assertion / contract checks are authored correctly, and that the resulting constrained undefined behavior does the expected thing.
The contract checks are code, and code needs testing.
Negative tests can be authored for codebases using the narrow `noexcept` rule or the throws nothing `noexcept` rule, but it is substantially easier to do so for narrow `noexcept` rule codebases.
### Core-language contracts ### {#arguments_core_language_contracts}
Usage of the proposed core-language contracts facilities will need to be tested.
`noexcept` will present testing challenges when violations of core-language contracts are configured to throw an exception.
Death tests are an option, but they have a large set of challenges on their own.
#### Put the contract check on the outside of the `noexcept` #### {#check_on_the_outside}
One of the design decisions that core-language contracts needs to make is to decide exactly where the checks will be run.
If precondition and post-condition checks happen within the callee (i.e. inside the `noexcept`), then failed preconditions and post-conditions checks set to throw will cause the program to terminate.
If those checks happen in the caller of the function (i.e. outside the `noexcept`), then at least one layer of throwing can still work in testing.
Deeply nested checks can still cause a terminate if they end up propagating through a different `noexcept`.
If precondition and post-condition checks in the caller of a `noexcept` function, then that would resolve most of the core-language contracts negative testing issues.
Putting preconditions and post-conditions outside the `noexcept` doesn't help with core-language contract asserts.
[[Voutilainen2780]] discusses this point, as well as discussing other benefits involving calling external libraries.
It also discusses the challenges of this approach with regards to indirect calls and multiply evaluated preconditions.
Caller-side precondition checking is likely to be less efficient in terms of code size compared to callee-side precondition checking.
#### Retrieve precondition and post-condition results via reflection #### {#reflection_for_contracts}
Suppose we have the following function + contract:
```c++
int f(int x) noexcept
pre (x >= 0)
post(r : r % 2 == 0);
```
Now imagine we had reflection facilities that let us do something like the following:
```c++
// // evaluate contracts of f(x) without calling f(x)
EXPECT_TRUE( $evaluate_pre(f, 20) );
EXPECT_FALSE( $evaluate_pre(f, -1) );
EXPECT_TRUE( $evaluate_post(f, 2, 20) ); // x = 20, returns 2
EXPECT_FALSE( $evaluate_post(f, 1, 0) ); // x = 0, returns 1
```
This would allow directly testing contracts without violating those contracts.
The tests would be very fast (no throws or terminates involved), and low maintenance.
A facility similar to this is proposed in [[Gustafsson3183]].
#### Allow contract build modes to change the behavior of `noexcept` #### {#contracts_change_noexcept}
Perhaps we consider `noexcept` the first core-language contract, with a default behavior of `std::terminate`.
Applying the `observe` semantic in combination with a throwing violation handler could change the behavior of `noexcept` to allow exceptions to escape a `noexcept` boundary.
This may even be something that should be extended to a general sad-path post-condition checking facility.
It would be nice if we could express that, on failure, `std::vector`'s single element insert should leave the container with the same `size()`, and the same `data()` pointer.
Making such sad-path post-condition checks throw on failure would also lead to early termination.
Allowing contract build modes to change the behavior of `noexcept` could be a concise way to unify `noexcept` and core-language contracts, but it could come at a substantial compatibility cost for those using core-language contracts.
If code was relying on `noexcept` terminating, then enabling `observe` semantics on `noexcept` will break their program.
This approach may be able to be combined with [[#magic_ub_exception]] to avoid major compatibility breaks.
This general approach is discussed in much greater depth in [[Ažman3205]].
### Extract contract predicates to a function ### {#extract_contracts_to_function}
A user could emulate the precondition reflection by following a good naming convention.
```c++
bool precondition_f(int x) noexcept;
bool postcondition_f(int retval, int x) noexcept;
int f(int x) noexcept {
assert(precondition_f(x));
int retval = 0;
/*...*/
assert(postcondition_f(retval, x));
return retval;
}
EXPECT_TRUE( precondition_f(20) );
EXPECT_FALSE( precondition_f(-1) );
EXPECT_TRUE( postcondition_f(2, 20) );
EXPECT_FALSE( postcondition_f(1, 0) );
```
This is an approach that is available for today's precondition and post-condition checking facilities.
A sophisticated user could write static analysis checks to enforce such a convention.
The author is unaware of any libraries or guidelines that do this though, which suggests that users either didn't think of it, or they don't see this approach as a good return on investment.
### The widely deployed implementations don't diagnose contract violations with exceptions ### {#throwing_not_used_by_big_impls}
[[Bergé1656]], [[Doumler2831]]
In 2023, the three most widely deployed standard libraries are libc++ (shipping with Clang), libstdc++ (shipping with GCC), and Microsoft's Visual Studio standard library (shipping with Microsoft Visual Studio).
None of these implementations use exceptions to diagnose contract violations.
### `noexcept` macro ### {#noexcept_macro}
[[MeredithN3248]], [[Doumler2831]]
Libraries can define a macro like the following:
```c++
#if TEST_ASSERTIONS
#define MY_NOEXCEPT
#else
#define MY_NOEXCEPT noexcept
#endif
```
This approach works, but it requires a large number of annotations.
Switching between the modes is detectable and increases the number of differences between the test environment and production environment.
Each difference between test and production decreases the value of the test.
### setjmp/longjmp ### {#setjmp_longjmp}
[[MeredithN3248]], [[Doumler2831]]
`setjmp` and `longjmp` can be used to bypass `noexcept` without triggering termination.
It is very difficult to use `setjmp` and `longjmp` without triggering undefined behavior though.
That makes `setjmp` and `longjmp` generally not viable.
### Child threads, fibers, and signals ### {#threads_fibers_signals}
[[Doumler2831]]
There are other creative ways to write contract handlers that permit testing while avoiding having an exception run into a `noexcept`.
[[Doumler2831]] describes some approaches for using (and leaking) threads that halt on failure rather than return.
There's also a description for how to use fibers / stackful coroutines to halt without consuming as many resources as the child thread approach.
Finally there's a signal based approach.
All of these approaches have substantial downsides.
None of these approaches are in wide use to the best of the author's knowledge.
### Potentially mitigating compiler features ### {#compiler_features}
There is the potential to address some of the testability concerns of `noexcept` with compiler extensions.
These ideas aren't new.
Some of them have been floating around for more than 10 years.
The fact that they haven't been implemented says something.
That something may be "Upstreaming compiler changes is an expensive prospect, and outside the capabilities of most organizations."
It may be "I'll do negative testing in a way that doesn't require a compiler feature."
Or it may be "Negative testing isn't sufficiently valuable to jump through these hoops."
So I'm not entirely sure what the lack of implementations says, but it certainly says something.
#### Compiler flag that makes `noexcept` unchecked #### {#make_noexcept_unchecked}
The `noexcept` specifier has two direct effects on the semantics of a program:
* exceptions leaving a `noexcept` function trigger `std::terminate`, and
* uses of the `noexcept` operator on expressions with the `noexcept` specifier change.
In theory, a compiler could add a non-conforming extensions that removes the `std::terminate` aspects, while keeping the `noexcept` operator aspects.
This would make it easier to test contracts, as then any exceptions could still escape `noexcept` functions.
In some ways, this is the inverse of [[Halpern2946]].
[[Halpern2946]] proposes a new attribute that (conditionally) keeps the `std::terminate` semantics, but doesn't modify the `noexcept` operator's behavior.
#### Exploiting precondition undefined behavior to ignore termination #### {#magic_ub_exception}
If a function does not meet its preconditions, then it is not required to meet its post-conditions.
A post-condition of `noexcept` functions is terminating when there's an exception.
An implementation could, conformingly, bypass the termination aspect of `noexcept` if the program has encountered undefined behavior.
In practice, this may be done by having a "magic" exception type (e.g. `nonstd::undefined_behavior_exception`) that bypasses `noexcept`.
Users would then be required to only use that exception when undefined behavior has been encountered.
### Death tests ### {#death_tests}
[[Bergé1656]], [[Doumler2831]]
A commonly recommended approach for negative testing is to use "Death tests".
With death testing, the code under test is run in a different process.
When the code under test executes a failing contract check, the code under test is expected to exit abnormally.
The exit value of the child process is checked in the parent process to ensure that the contract check was triggered appropriately.
Multiple standard libraries use death tests for their negative testing.
The largest complaint regarding death testing is the performance penalty.
Launching a process is orders of magnitude more expensive than throwing an exception, and this can inflate test times from seconds to minutes when large numbers of negative tests are present.
The performance penalty is particularly large on Microsoft Windows.
Traditionally, death tests can communicate a very limited amount of information from the child process to the parent process, typically one integer or less.
This makes it difficult to know whether the child process's abnormal termination was from the intended contract check failure, or some other reason.
In theory, the failing checks could communicate through some other out-of-band channel (a file on disk, standard out / error, shared memory), but this isn't currently common practice.
DejaGnu, the testing framework used by libstdc++, permits users to check the process-under-test's return code, as well as run regex's on standard out and standard error, though libstdc++ currently only looks at the return code.
Launching child processes has other hazards as well.
Using the POSIX `fork()` call while code under test is running in another thread introduces substantial testing risks and uncertainty.
If the code under test is not `fork()` safe, then death tests can't be launched with `fork()` while the code under test is running.
This can add yet-another performance penalty, as it forces the test to re-run potentially expensive setup code.
The test framework and code under test also have to take care not to run `fork()` unsafe code, which can be challenging when global constructors are involved.
A mitigation for this is to have a test launcher that is entirely distinct from the code under test.
`spawn()` and `clone()` calls can also be used, but with their own draw backs.
Death tests have portability issues.
C++ can run in a variety of environments, and death tests are mostly suited to desktop and server environments.
Implementing death tests in browsers and embedded / microcontroller environments would be very challenging, as they typically don't support multiple processes.
In addition, most testing frameworks don't support death tests, with GoogleTest and DejaGnu being the main test frameworks with that support.
GoogleTest mixes the code under test and the test infrastructure in the same process(es).
Using exceptions to diagnose precondition violations in production {#precondition_exception_in_production}
-----
### Security dangers of throwing exceptions while in an unknown state ### {#throwing_security}
In security sensitive contexts, running out of contract is very dangerous.
Each instruction executed while out of contract is another opportunity for an attacker to do as they please with the system.
Programs should run as little code as possible after detecting contract violations.
This is typically done by exiting the program as quickly as possible.
Throwing an exception runs a lot of instructions.
Many of those instructions are indirect code branches, which are particularly dangerous.
The Microsoft Visual Studio 32-bit x86 implementation has a build flag (`/SAFESEH`) to harden their exception handling implementation against attacks attempting to leverage those indirect code branches, but the number of instructions run is still very high.
### Temporary continuation ### {#temporary_continuation}
[[Lakos2861]]
Following the narrow `noexcept` policy is more permissive in terms of allowing temporary continuation than the throws nothing policy.
Some users of C++ desire temporary continuation after contract violations, and the throws nothing policy prevents temporary continuation in many places.
### Likely to cause more UB or hit a destructor `noexcept` ### {#more_ub_and_noexcept}
Writing exception safe code often requires using operations that aren't allowed to throw.
A common pattern is to "do all the work off to the side, then commit using nonthrowing operations only" [[Sutter82]].
If one of the non-throwing operations ends up throwing due to a contract violation, then additional library UB has been added to the program, beyond the initial contract violation.
Throwing from a non-throwing operation can also cause termination in destructors, which are `noexcept` by default.
So if a contract is violated in a throws nothing function called from a destructor, then termination is the behavior, even if continuation is the desire.
Both of these points illustrate the difficulty and danger in requiring a program to never terminate, even when aided by throwing contract handlers.
### Postconditions only hold when preconditions hold ### {#garbage_in_garbage_out}
[[Krzemieński2858]]
A general programming principle is that postconditions aren't required to hold when preconditions don't hold.
This is often paraphrased as garbage in, garbage out.
If `noexcept` is a postcondition, then the termination aspect is not required to hold if the precondition doesn't hold.
[Here](https://godbolt.org/z/36nbY48Er) is an example program where the violation of a library precondition leads to language undefined behavior, with an end result of `noexcept` being bypassed by an exception.
### Precondition UB and `noexcept` contradict? ### {#noexcept_contradiction}
[[Doumler2831]]
[[Doumler2831]] argues that `noexcept` and precondition undefined behavior contradict, as any behavior should be permitted.
`noexcept` prevents the implementer of the function from manifesting some of those behaviors.
For user code, the extent of their undefined behavior is indeed constrained by `noexcept`.
Standard library implementations are under no such restriction.
[[#magic_ub_exception]] describes one way that an implementation could choose to throw an exception past `noexcept`, if they deemed it worth the implementation effort.
Termination risk {#termination_risk}
-----
[[Doumler2831]], [[Lakos2861]], [[Lakos2949]]
Using `noexcept` can insert code paths that invoke `std::terminate`.
The terminating code paths are often unreachable code removed during translation, but this isn't always the case.
There are applications that prefer library UB over `std::terminate` when faced with a failing contract.
Those libraries may use exceptions to indicate failed contracts.
For safety critical systems (note the use of the term "systems", not applications), undefined behavior is generally considered worse than termination, even if the undefined behavior is "only" library UB.
In these systems, an individual application may terminate, but the system as a whole has ways to recover from a terminated application, or to put the system into a safe state.
Philosophy {#philosphy}
-----
### `noexcept`-accuracy ### {#noexcept_accuracy}
[[Bergé1656]], [[Doumler2831]], [[Halpern2946]]
There is a desire to document the exception behavior of a function in the signature of that function.
This documentation helps compilers, static analyzers, and people reading the code to understand what the function does.
[[Krzemieński2858]] makes the distinction between functions that can't throw and functions that can't fail.
C++'s `fclose` [can't throw](https://eel.is/c++draft/library#res.on.exception.handling-2) (POSIX's can as part of thread cancellation). `fclose` can fail though, as part of `fclose` is flushing buffer contents to storage, and that storage may not be available at time of close (imagine a USB drive being removed).
This separation between can't throw and can't fail makes it more difficult to determine what constitutes `noexcept`-accuracy.
### Doesn't matter since implementations can already add `noexcept` ### {#can_already_add_noexcept}
[[Doumler2831]], [[Bergé1656]]
If the narrow `noexcept` policy is in place, implementations can strengthen the `noexcept` annotations on their functions.
Major implementations have done so.
Strengthening `noexcept` in the standard would end up adding very few `noexcept` annotations in the major implementations.
### Standard library policies vs. C++ library policies ### {#stdlib_vs_user_lib_policies}
[[Lakos2861]], [[Doumler2831]]
Many libraries attempt to emulate the design policies of the standard library.
The standard library should try to set a good example for third-party and end-user libraries, as others will use the standard library as an example whether the committee thinks they should or not.
The standard library needs to accommodate all domains, serve millions of programmers, and billions of users.
Most other libraries aren't as widely used.
In some libraries, the customers are known well enough that a compatibility hit from changing `noexcept` can be absorbed.
The domain and use cases are known well enough that termination on precondition violation can be deemed suitable.
For some libraries, improving some performance aspect (like binary size) by half a percent is worth increasing the cost of testing by a factor of 100.
The more heavily used a library, the more likely it is that such a trade-off is worthwhile.
Few libraries are used more heavily than the STL.
### Standard library implementation strategies need not be encoded in the C++ standard ### {#impl_strategies_in_the_std}
[[Lakos2861]]
For some standard library implementations, the correct technical and business choice is to terminate on precondition failure via a `noexcept` decoration.
That is a valid implementation strategy.
However, that doesn't mean that this implementation should be put in the standard, where it constrains other implementations that don't want to make that choice.
### Cleanup operations often have preconditions, and need to not throw ### {#cleanup_preconditions}
[[Krzemieński2858]]
Cleanup operations, like `delete`, `fclose`, and `std::lock_guard::~lock_guard` all have preconditions, and all need to be made to not throw.
If the preconditions on these functions were checked and made to throw, they would cause the `noexcept` on destructors to terminate.
If the destructors were marked `noexcept(false)`, you would still run the risk of termination from when unwinding triggered a precondition fault, causing two exceptions to be active at the same time.
### Narrow `noexcept` ignores implicit precondition of indeterminate values and invalid objects ### {#invalid_and_indeterminate}
[[Lakos2949]]
The narrow `noexcept` policy is defined in terms of wide and narrow contracts, but even the wide contracts have their limits.
Take `std::string::c_str()` as an example.
`c_str()` is safe to call on any valid `std::string`, even the empty string.
However, if the bytes composing the `std::string` are corrupted in some way, then `c_str()` is likely to dereference an invalid pointer.
This means that `c_str()` (and most functions) have a precondition of basic object validity.
If wide contracts are expanded to encompass functions that work with invalid objects, then very little has a wide contract.
### Don't discard useful information ### {#dont_discard_useful_info}
The "no-throw" guarantee is useful information to the compiler, to developers, for making algorithmic decisions, and for generic transformations.
Expressing this through `noexcept` can satisfy each of these use cases.
Can't write narrow `noexcept` on top of throws nothing {#narrow_above_throws_nothing}
-----
[[Meredith2837]]
If part of a system is written with a throws nothing policy, then the system as a whole loses the narrow `noexcept` property.
Systems built with libc++, libstdc++, and the Microsoft Visual Studio standard library generally do not have the narrow `noexcept` property.
Other libraries can get many of the benefits of narrow `noexcept`, even if the system as a whole doesn't have the narrow `noexcept` property.
Negative testing can be performed on narrow `noexcept` libraries, as exceptions usually don't need to travel far.
Preconditions can be checked in production for all the code between entry points and the first non-narrow `noexcept` code.
Non-narrow `noexcept` libraries that interact heavily with callbacks can get in the way of both precondition checking and negative testing.
`noexcept` as Quality of Implementation {#noexcept_as_qoi}
-------
[[Wakely3201]] sets the policy that library wording should not use `[[nodiscard]]`.
Its rationale is that warnings are non-normative, and that implementers are better qualified to determine when and where warnings should be emitted.
Perhaps such an approach could be taken with `noexcept`, where the standard does not mention (or rarely mentions) `noexcept`, and implementers decide where `noexcept` should go.
Arguably, this is close to the status quo, as the standard gives permission for implementations to strengthen their `noexcept` guarantees.
Following such a principle leads to the minimal `noexcept` policy [[#wording_minimal_noexcept]].
A notable difference between `[[nodiscard]]` and `noexcept` is that the result of the `noexcept` operator is normative, where warnings are not.
This makes `noexcept` observable and normative.
In practice, changing `noexcept` won't affect the semantics of most programs so long as it isn't changed on a move operation. [[#backwards_compat]].
Notable Omissions {#notable_omissions}
=================
This paper's discussions focus almost entirely on rule "b" in the `noexcept` policy.
The rationale for the other rules haven't been explored nearly as much.
Given that, I will list some known issues that should be considered unaddressed, should someone decide to challenge the other rules.
Asynchronous callbacks {#async_callbacks}
----------------
[[StdExec2300]] uses the `noexcept` operator in conjunction with concepts to make compile-time decisions.
The proposed policies do not place any requirements on concepts or user facilities, but the policies do place restrictions on functions.
Many [[StdExec2300]] functions are marked `noexcept`, but it is unclear to the author of this paper whether any of those functions have preconditions.
This paper does not attempt to craft policy regarding `noexcept` and asynchronous callbacks.
Throwing destructors {#throwing_destructors}
-------
Destructors that `throw` exceptions are uncommon, and usually a bad idea.
However, there are some categories of objects where doing so is useful, like `scope_success` in the library fundamentals v3 TS [[LFTS4948]].
This paper does not attempt to craft policy that distinguishes when having a throwing destructor is acceptable.
Pervasive conditional `noexcept` {#pervasive_conditional}
-----------
Some library authors have expressed desires to standardize conditional `noexcept` operations for facilities like duration math in `chrono`.
`chrono::duration` instantiations involving built in integers have many non-throwing wide contract operations that become throwing when using "BigNum" types.
The proposed policies prohibit standardizing conditional `noexcept` for functions other than some special member functions.
This paper provides no rationale for this choice.
C compatibility {#c_compat}
-----------
The proposed policy permits C compatibility facilities to be marked unconditionally noexcept.
This paper provides no rationale for this choice.
Principled design {#principled_design}
==========
[[Lakos3004]] describes a process of enumerating, ranking, and scoring design principles as a way to aid decision making and communication.
The associated paper [[Lakos3005]] includes a worked version discussing `noexcept` policy variations.
This paper will present an alternative compliance table, as well as discuss some of the big differences between the [[Lakos3005]] table, and the one presented here.
[[#appendix_principled_design]] includes a (mostly) worked version where John Lakos and Ben Craig worked through the example together.
Principle descriptions {#principle_descriptions}
--------------------
### Shared principles ### {#shared_principle_descriptions}
The following principles are shared between [[Lakos3005] and this paper, though the wording may be slightly modified.
* AlgoOpt: Maximize algorithmic runtime optimizations. [[#algorithmic_optimizations]]
* MinExeSize: Minimize executable and shared library size. [[#size_bloat]]
* MinObjSize: Minimize object-code size. [[#size_bloat]]
* MaxExpressInCode: Maximize expressing defined behavior directly in code and the standard. [[#noexcept_accuracy]]
* MinUnintendedExit: Minimize the chance that a propagated exception will cause unintended termination. [[#termination_risk]]
* MaxNegUnitTest: Maximize the return on investment for negative unit testing of standard functions. [[#library_testing]]
* MinCostToImpls: Minimize effort for implementations to remain conformant while satisfying their client base.
* MinCostToFixStd: Minimize effort to align the current Standard Library with this policy statement.
* MinPermDiverge: Minimize likely persistent discrepancies between the Standard and this policy statement.
* MaxUserPortability: Maximize portability for user code written using the Standard Library.
### New principles ### {#new_principle_descriptions}
The following principles are new in this paper.
* TableBasedPerf: Maximize runtime performance on table based implementations. [[#modern_impls]]
* NonTablePerf: Maximize runtime performance on non-table based implementations. [[#other_impls]]
* MinUnsafe: Minimize utility of unsafe, insecure, and misleading patterns. [[#throwing_security]], [[#more_ub_and_noexcept]]
* FutureWidening: Maximize committee's ability to widen function contracts in the future [[#backwards_compat]]
* MaxContractRecovery: Maximize user's ability to recover from a contract violation without terminating [[#temporary_continuation]], [[#termination_risk]]
### Omitted principles ### {#omitted_principle_descriptions}
The following principles from [[Lakos3005]] are not scored or ranked in this paper.
* ArgRestrict: Maximize use of noexcept to qualify function parameters.
* AllowOnAsyncFrame: Allow noexcept on customization-point functions used in asynchronous contexts.
These principles seem to be related to questions involving asynchronous callbacks.
This paper is choosing to not address asynchronous callback noexcept questions at this time. [[#async_callbacks]]
* MaxUserSafeShut: Maximize implementations’ ability to enable users to achieve safe shutdown in production.
* MaxUserSafeRecov: Maximize implementations’ ability to enable users to handle and continuing in production.
These principles are replaced by MaxContractRecovery.
In addition, these principles conflate safety, shutting down cleanly, and recovery with throwing contract-violation handlers.
If future revisions of this paper are needed, then more detailed arguments about safe shutdown and safe continuation can be provided. [[#precondition_exception_in_production]] contains some discussion.
* MaxImplFlexHand: Maximize implementations’ ability to support throwing contract-violation handlers.
This principle is replaced by MaxContractRecovery.
MaxImplFlexHand isn't directly useful.
If a contract-violation handler throws, and almost immediately terminates due to it hitting a `noexcept`, then that is operating as specified, and therefore easy to support.
MaxContractRecovery attempts to capture the use case of contract failures being recoverable.
* MaxBestPracExamp: Maximize a desirable best-practices model (safety/performance) for typical retail users.
[[#stdlib_vs_user_lib_policies]] discusses the topic.
Scoring this topic is challenging, as what constitutes the best practice is up for debate.
This means that the scoring for such a policy is circular, in that we need to know the solution outcome to know a proper score for the principle.
As a result, I chose not to score this policy.
* MaxUseForAlgoNeed: Maximize ability to use noexcept for narrow contracts having algorithmic need.
This seems like a duplicate of AlgoOpt "Maximize algorithmic runtime optimizations".
* MinDivergMeaning: Minimize divergence in meaning of standard functions across conforming implementations.
This seems to be a duplicate of MaxUserPortabilty "Maximize portability for user code written using the Standard Library".
Divergence in behavior causes portability issues.
* MaxMultiverse: Minimize the likelihood of disenfranchising a member of the multiverse. [[#narrow_above_throws_nothing]]
This is more of a meta-principle.
This meta-principle encourages prioritizing cutting solutions with low principle scores, even when a given solution may win out on more important principles.
Solution descriptions {#solution_descriptions}
--------------------
This paper describes two solutions, but represents it as three columns in the compliance table.
The narrow `noexcept` solution is represented twice, once for the "minority" implementations that only use `noexcept` where mandated by the standard, and once for the "majority" implementations that place `noexcept` on most functions that throw nothing (as permitted by the standard).
This will allow readers to gauge the impact in the different implementations.
* MinNar: Minority narrow `noexcept`. The minority implementations place `noexcept` only where required.
* MajNar: Majority narrow `noexcept`. These implementations place `noexcept` on most "throws nothing" functions. Closest to status quo.
* TN: Throws nothing rule
Most of the standard library is specified using the narrow `noexcept` design principle.
It _almost_ counts as the status quo policy.
However, we currently don't have a formal policy.
This no-policy status quo is not represented in the table, as it is very difficult to reason about the properties of an "anything goes" approach.
This is one of the reasons the author would prefer _a_ policy, even if it is not the author's preferred policy.
MinNar and MajNar correspond to solution C (MAX+LAK+NLC) in [[Lakos3005]].
TN corresponds to solution B (MAX) in [[Lakos3005]].
The author did not have the time to apply the principled design approach to the large variety of options explored in [[Lakos3005]]. There's only so much time between the February 2024 mailing and the 2024 March Tokyo meeting.
Compliance table {#compliance_table}
----------------
<table>
<tr>
<th>Rank</th>
<th>Importance</th>
<th>Objectivity</th>
<th>Principle ID</th>
<th>MinNar</th>
<th>MajNar</th>
<th>TN</th>
</tr>
<tr>
<td>1</td>
<td>9</td>
<td>@</td>
<td>AlgoOpt</td>
<td>9</td>
<td>9</td>
<td>9</td>
</tr>
<tr>
<td>2</td>
<td>9</td>
<td>9</td>
<td>TableBasedPerf</td>
<td>9</td>
<td>9</td>
<td>9</td>
</tr>
<tr>
<td>3</td>
<td>5</td>
<td>9</td>
<td>NonTablePerf</td>
<td>5</td>
<td>9</td>
<td>9</td>
</tr>
<tr>
<td>4</td>
<td>5</td>
<td>9</td>
<td>MinExeSize</td>
<td>5</td>
<td>9</td>
<td>9</td>
</tr>
<tr>
<td>5</td>
<td>5</td>
<td>5</td>
<td>MaxExpressInCode</td>
<td>3</td>
<td>5</td>
<td>7</td>
</tr>
<tr>
<td>6</td>
<td>5</td>
<td>5</td>
<td>MaxUserPortability</td>
<td>7</td>
<td>7</td>
<td>9</td>
</tr>
<tr>
<td>7</td>
<td>5</td>
<td>5</td>
<td>MinUnintendedExit</td>
<td>7</td>
<td>5</td>
<td>5</td>
</tr>
<tr>
<td>8</td>
<td>5</td>
<td>5</td>
<td>MaxNegUnitTest</td>
<td>7</td>
<td>3</td>
<td>3</td>
</tr>
<tr>
<td>9</td>
<td>5</td>
<td>5</td>
<td>MinUnsafe</td>
<td>3</td>
<td>7</td>
<td>7</td>
</tr>
<tr>
<td>10</td>
<td>5</td>
<td>-</td>
<td>MinCostToImpls</td>
<td>9</td>
<td>9</td>
<td>7</td>
</tr>
<tr>