Type pattern matching

To find and replace a type, create a struct that inherits publicly from bronto::rewrite_type (either directly or indirectly) and apply the BRONTO_BEFORE() and BRONTO_AFTER() annotations to type aliases identifying the type to match and its replacement.

Example
struct LegacyStringToStdString : bronto::rewrite_type {
  using before BRONTO_BEFORE() = legacy::String;
  using after BRONTO_AFTER()   = std::string;
};

The annotation is written between the name of the alias and the =. Every place the matched type is written will be rewritten, including variable declarations, function parameters, return types, base classes, data members, template arguments, the right-hand side of other type aliases, and type-only contexts such as sizeof.

BRONTO_BEFORE

Requirements

Each struct inheriting from bronto::rewrite_type must have at least one member type alias marked BRONTO_BEFORE(). The annotation must be applied to a type alias. Applying it to a function, struct, or any other member is an error.

Semantics

The aliased type defines the pattern to be matched. The pattern names entities rather than spellings, so a pattern of old_ns::Old matches every use of that type regardless of how it happens to be qualified at the use site. A different entity that shares the name (::Old in the example below) does not match.

Namespace qualification
using Old = int;

namespace old_ns {
struct Old {};
}  // namespace old_ns

struct R : bronto::rewrite_type {
  using before BRONTO_BEFORE() = old_ns::Old;
  using after BRONTO_AFTER()   = new_ns::New;
};
 namespace old_ns {
-static_assert(sizeof(Old) == 1);
+static_assert(sizeof(new_ns::New) == 1);
 static_assert(sizeof(::Old) == sizeof(int));
 }  // namespace old_ns

If any qualifiers (const or volatile) are written in the pattern, the matched type must also have them. If they are not written in the pattern, the type may (but is not required to) be qualified, and any qualifiers it does have are preserved in the replacement. With a pattern of Old and a replacement of New const:

-void h(Old volatile);
+void h(New const volatile);

Template parameters

Template parameters of an alias template act as "holes". A type parameter binds to a type argument and a non-type parameter binds to the argument expression. Bound arguments are used as written rather than evaluated, so the rule

struct Rule : bronto::rewrite_type {
  template <typename T, int N>
  using before BRONTO_BEFORE() = DepArray<T, N>;
  template <typename T, int N>
  using after BRONTO_AFTER() = T[N * 2];
};

produces

-  DepArray<int, 3 * 2> b;
+  int b[3 * 2 * 2];

Because arguments are matched as written, a parameter appearing more than once in a pattern requires the same spelling at each occurrence. A pattern of S<T, T, T> matches S<int, int, int> and S<Int, Int, Int>, but does not match S<Int, int, int>, even when Int is an alias for int.

Template arguments the pattern leaves unwritten are still matched against their defaults. If S is declared as

template <typename T, typename U = void>
struct S {};

then a pattern of S<T> matches both S<int> and S<int, void>, but does not match S<int, int>.

BRONTO_AFTER

Requirements

Each struct inheriting from bronto::rewrite_type may have at most one member type alias marked BRONTO_AFTER(). It must declare the same number of template parameters as each alias marked BRONTO_BEFORE(). A rule with no BRONTO_AFTER finds matches but rewrites nothing.

Semantics

The aliased type is the replacement for whatever the pattern matched. Each bound template argument is used in place of the template parameter of the same name.

The replacement is written with the qualification it needs at each site, so a replacement of new_ns::New is written New at sites already inside new_ns.

The replacement may use any type constructors, including pointers, references, arrays, and function types. Bronto rebuilds the declarator at each use, adding parentheses wherever C++ requires them. For instance, the rule

template <typename T>
struct Pointer {};

struct Rule : bronto::rewrite_type {
  template <typename T>
  using before BRONTO_BEFORE() = Pointer<T>;
  template <typename T>
  using after BRONTO_AFTER() = T*;
};

produces

-  Pointer<int[3]> const* volatile d;
-  Pointer<int[3]>* h[5];
-  Pointer<int[3]>(*i)[5];
+  int (*const* volatile d)[3];
+  int (**h[5])[3];
+  int (*(*i)[5])[3];

Limitations

  • A type that is not written at the use site, such as one deduced through auto, has nothing to rewrite. Class template argument deduction is not yet supported.
  • Uses of a type named through a using-declaration (using old_ns::Old;) are not reliably rewritten.
  • Rewrite rules cannot be nested inside a bronto::rewrite_type rule, and a bronto::rewrite_type rule cannot be used as a BRONTO_USAGE constraint.

Relationship to type inlining

A bronto::rewrite_type rule replaces one type with another. To replace an alias with the type it already names, annotate that alias with BRONTO_INLINE() instead, as described in type inlining.

On this page