Constant evaluation in replacements
In a replacement, bronto::eval(expr) constant-evaluates expr and writes the
result into the rewritten source. For example, bronto::eval(1 + 2) writes the
value 3 rather than the spliced text 1 + 2. Captured expressions can appear
inside expr, so a replacement can compute a new constant from the values
matched at each site.
constexpr int Square(int n) { return n * n; }
struct SquareTheArgument : bronto::rewrite_expr {
BRONTO_BEFORE()
void find(int n) { foo(n); }
BRONTO_AFTER()
void repl(int n) { bar(bronto::eval(Square(n))); }
};With this rewrite, the call foo(3) becomes bar(9). Without bronto::eval it
would instead become bar(Square(3)).
bronto::eval
Requirements
bronto::eval takes exactly one argument and may only appear in a function
annotated with BRONTO_AFTER(). Using it in a function annotated with
BRONTO_BEFORE() is an error.
After the captured expressions are substituted for the parameters they bind to, the argument must be a constant expression. If the substitution fails, or the substituted expression cannot be constant-evaluated, the matched site is left unchanged and a diagnostic explains why.
Semantics
The argument is constant-evaluated and the resulting value is written into the replacement as a literal denoting the value exactly. The type of the expression determines the kind of literal that is generated.
| Expression type | Generated literal | Example |
|---|---|---|
bool | boolean literal | true |
int | integer literal, no suffix | 42 |
unsigned | integer literal, u suffix | 42u |
long | integer literal, l suffix | 42l |
unsigned long | integer literal, ul suffix | 42ul |
long long | integer literal, ll suffix | 42ll |
unsigned long long | integer literal, ull suffix | 42ull |
signed char, unsigned char, short, unsigned short | static_cast of an integer literal | static_cast<short>(1000) |
char | character literal, no prefix | 'a' |
wchar_t | character literal, L prefix | L'a' |
char8_t | character literal, u8 prefix | u8'a' |
char16_t | character literal, u prefix | u'a' |
char32_t | character literal, U prefix | U'a' |
float | floating-point literal, f suffix | 1.5f |
double | floating-point literal, no suffix | 1.5 |
long double | floating-point literal, L suffix | 1.5L |
| string-like (see below) | string literal, prefix matching the code unit | "abc", L"abc" |
| character pointer (see below) | string literal, prefix matching the code unit | "abc", L"abc" |
Character results
Printable ASCII characters are written verbatim, mnemonic escapes such as \n
are used where they exist portably, and any other value uses a numeric escape.
Note that signed char and unsigned char are treated as integer types, not
character types.
Floating-point results
The expression is constant-evaluated with IEEE 754 arithmetic in the result
type's format on the target described by your compile_commands.json. For
example, the format of long double varies by target. The following applies:
- Values are rounded to nearest with ties to even.
- Operations are never contracted. For example,
a * b + cis evaluated as a rounded multiply followed by a rounded add, never as a fused multiply-add, so the result may differ from what the same expression would compute at runtime under flags like-ffp-contract=fast. - The emitted literal denotes the evaluated value exactly. Any compiler that parses floating-point literals with correct rounding reproduces the value bit for bit. It is not necessarily the shortest spelling of that value.
String results
To output a string literal, the evaluated expression must be of a type that has
a constexpr data() member function returning a pointer to a standard character
type and a constexpr size() member function returning its length.
std::string_view satisfies these requirements in C++17, and std::string does
as well in C++20. The literal denotes each code unit of the result exactly,
using the same escaping rules as character literals.
An expression whose type is itself a pointer to a standard character type, such
as char const*, is also accepted. It is read as a NUL-terminated string. The
pointer must point to a NUL-terminated constant, so a null pointer or an array
without a terminator fails to constant-evaluate.
constexpr std::string KebabCase(std::string_view s) {
std::string result;
for (char c : s) { result.push_back(c == '_' ? '-' : c); }
return result;
}
struct MigrateOptionNames : bronto::rewrite_expr {
BRONTO_BEFORE()
void find(char const* name) { get_option(name); }
BRONTO_AFTER()
void repl(char const* name) { get_flag(bronto::eval(KebabCase(name))); }
};With this rewrite, the call get_option("max_retry_count") becomes
get_flag("max-retry-count"). The string literal captured at each call site is
substituted for name before the expression is constant-evaluated, so every
call site gets its own precomputed literal.