Audience: EWG
S. Davis Herring <>
Los Alamos National Laboratory
June 17, 2019
Discussion of the mechanisms for controlling contract evaluation and assumption continues; see my P1494R0 as well as P1490R0 for a review of some recent concerns involving optimization. Papers like P1332R0 have also proposed significant extensions to those mechanisms for the purposes of better control and scalability.
Fundamentally, a contract (that is, a contract-attribute-specifier) is a boolean expression whose evaluation and significance can be controlled non-locally (by, e.g., the continuation mode). The common protocol (across many files of diverse authorship) for such control is the principal reason that that contracts exist as a language feature. The alternative, per-library approach is of course already available:
namespace my_lib {
struct Stuff {/*...*/};
bool check_expensive(),keep_going();
bool bad_news(const Stuff&);
void do_stuff(const Stuff &s) {
if(check_expensive() && bad_news(s))
if(keep_going()) std::fprintf(/*...*/);
else throw /*...*/;
s.do_it();
}
}
It is also worth noting that concerns about optimization and assumptions in particular are subject to the normal implementation-supplied control over such analyses (-fno-assume-contracts
wouldn’t surprise anyone and would be implied by -O0
). All that being said, the usability concerns raised for C++20 and for the future are very real.
This paper proposes, as a bug fix to address these issues, to replace the current contract-level with a set of restrictions that limit the effect of the global controls, to be applied when local conditions (including the recency of a contract’s introduction) make it unsafe to take full advantage of the consistent control. The local restrictions only reduce the set of behaviors available via the global controls for a construct; the only direct control (along the lines of P1334R0) provided is a guaranteed check. The proposal also stops well short of an extensible roles system as proposed (though not for C++20) by P1332R0. One additional global control is also proposed.
Augment the build level and violation continuation mode with one additional global control, the assumption mode, that when off restricts contract condition evaluation to those that are guaranteed to be evaluated. That is, it suppresses the controversial passage from [dcl.attr.contract.check]/4:
it is unspecified whether the predicate for a contract that is not checked under the current build level is evaluated; if the predicate of such a contract would evaluate to
false
, the behavior is undefined.
In the place where one of default
, audit
, or axiom
may currently appear, allow any combination of the following modifiers with the given semantics:
tentative
|
the expression may not be assumed if it is not evaluated |
halt
|
the continuation mode is taken to be off for this contract |
static
|
the expression is never evaluated |
audit
|
the expression may not be evaluated if the build level is not audit |
always
|
the expression is always evaluated at all build levels |
A new contract may be introduced with the tentative
restriction to avoid adding undefined behavior to existing interfaces; this is similar to the continue
proposed in P1290R1. Note that the contract condition may still be assumed in code that follows it if it is checked and the continuation mode is off.
If the global assumption mode and continuation mode are on, a halt
contract may still be assumed in code that follows it. The static
restriction replaces the use of axiom
for static analysis, removing the overloading with its use for assumptions for optimization. The role played by audit
is much the same as in the current draft. The principal purpose of always
is to allow the contract syntax to be used with no global control at all (in which case the only reasonable behavior is checking).
P1332R0 proposes several features to satisfy its extensive set of use cases. Several of these are covered by this simpler proposal:
ignore
semantic as a choice for a contract level is provided by the global assumption mode.%review
tag is addressed by the tentative
restriction, perhaps used in conjunction with a throwing handler.always
modifier (in the case where it is not trivially equivalent to certain if
statements).audit
and normal contracts is supported by generally using a continuation mode of on and adding halt
to checks past which it is known to be unsafe to continue.Most of the others may be addressed elsewhere:
#include
).check_maybe_continue
optimization barrier is realized more generally by std::observable
from P1494R0.check_always_continue
is not addressed, but it appears to be outside the scope of the language to restrict optimization in such a fashion.
Wording to be provided (post-haste) in case of EWG approval.