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.

Example
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 typeGenerated literalExample
boolboolean literaltrue
intinteger literal, no suffix42
unsignedinteger literal, u suffix42u
longinteger literal, l suffix42l
unsigned longinteger literal, ul suffix42ul
long longinteger literal, ll suffix42ll
unsigned long longinteger literal, ull suffix42ull
signed char, unsigned char, short, unsigned shortstatic_cast of an integer literalstatic_cast<short>(1000)
charcharacter literal, no prefix'a'
wchar_tcharacter literal, L prefixL'a'
char8_tcharacter literal, u8 prefixu8'a'
char16_tcharacter literal, u prefixu'a'
char32_tcharacter literal, U prefixU'a'
floatfloating-point literal, f suffix1.5f
doublefloating-point literal, no suffix1.5
long doublefloating-point literal, L suffix1.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 + c is 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.

String Example
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.

On this page