Skip to content

Metaprogramming Introductory Tutorial

Wyatt Childers edited this page Oct 7, 2020 · 37 revisions

Preface

This tutorial assumes that you're familiar with our implementation of static reflection. If that is not the case, or you need to brush up, you can find our static reflection tutorial here.

Additionally, this tutorial assumes the presence of a compiler built from the feature/metaprogramming branch.

Note: If you are only looking to experiment a bit with the compiler, and go through these examples, you can use Compiler Explorer which is updated on a nightly basis, against feature/metaprogramming, and can be used with minimal effort.

Background

We provide expanded versions of our two header libraries for working with reflection, so that they are better suited for metaprograms:

These libraries provide a number of functions to allow you to work with compile time reflections, and to assist you in building metaprograms.

Example Preface

All complete examples unless otherwise specified are assumed to begin with:

#include <experimental/meta>
#include <experimental/compiler>

using namespace std::experimental;

Additionally, it is assumed that all examples are compiled with the compiler flags:

-std=c++2a -freflection

Fragments

A fragment can be thought of a snippet of "template code" in that, it's code which is intended not to be executed, but to be injected, and transformed into executable code.

All fragments support explicit capture of variables in their enclosing context via the unquote operator -- this allows a degree of customization to a fragment. All fragments can similarly, take any number of elements that would be found in their context traditionally. For instance, anything valid inside of a namespace, is valid inside of a namespace fragment.

Fragments are represented as reflection values, and as a result, have type meta::info.

Currently we have support for namespace, class, enum, and block fragments.

Namespace Fragment

A namespace fragment can be declared as follows, with or without a name:

fragment namespace {
};

fragment namespace a {
};

Class Fragment

A class fragment can be declared as follows, with or without a name:

fragment class {
};

fragment class a {
};

fragment struct {
};

fragment struct a {
};

As with a normal class, the choice between class and struct controls only the default access level of the contents. That's to say things injected from a class fragment will implicitly have private access, and things injected from a struct fragment will implicitly have public access.

Enum Fragment

An enum fragment can be declared as follows, with or without a name:

fragment enum {
};

fragment enum E {
};

Block Fragment

A block fragment can be declared as follows, and never has a name:

fragment {
};

The Unquote Operator

The unquote operator looks like %{} and appears in fragments to escape to the enclosing context, and capture a value.

A trivial example, in the following code the expression foo + 10 will be parsed as if it appeared before the fragment. Then during evaluation, foo + 10 will be evaluated as rvalue constant expression.

int foo = 10;
fragment {
  return %{foo + 10};
};

This evaluated value (20) will then replace the capture %{foo + 10} when injected, as if you had instead written:

fragment {
  return 20;
}

Injection

At the core of C++ metaprogramming, you're simply generating new code. A C++ metaprogram cannot modify an existing declaration. That's to say, if you declare a global variable:

int foo = 0;

There is no way for a metaprogram to change foo. However, a metaprogram can reflect upon foo, and apply a modifier to it, allowing you to generate, new code based off of foo.

There are two ways to inject code. A metaprogram declaration paired with an injection statement, and an injection declaration.

Metaprogram Declaration & Injection Statements

A metaprogram declaration, looks like the following:

consteval {
  ...
}

A metaprogram declaration, contains any number of statements, and can be thought of as a special consteval function which does not take arguments, and which is automatically executed.

To inject code using a metaprogram declaration, you must use an injection statement. An injection statement can accept either a reflection, or a fragment.

As an example, this might look like the following:

consteval {
  meta::info foo_reflection = reflexpr(foo);
  -> foo_reflection;
}

Injection Declaration

An injection declaration, looks like the following:

consteval -> ...;

An injection declaration can be thought of as a shorthand for a metaprogram declaration paired with an injection statement.

For instance, we could rewrite the Metaprogram Declaration example more concisely as:

consteval -> reflexpr(foo);

Declaration Cloning

Unmodified Declaration Cloning

Cloning a declaration without modification is perhaps the simplest of all possible metaprograms. Take the following source code:

namespace a {
  int foo() { return 0; }
}

Now let's say we want an exact copy of function foo inside of namespace b. So the equivalent code that we want to write a metaprogram for would be:

namespace a {
  int foo() { return 0; }
}

namespace b {
  int foo() { return 0; }
}

Our metaprogram using the simplest option -- the injection declaration -- would look as follows:

namespace a {
  int foo() { return 0; }
}

namespace b {
  consteval -> reflexpr(a::foo);
}

Modified Declaration Cloning

Building on the previous example, and adding a level of complexity. Let's say we wanted to instead inject a constexpr version of foo. To do this easily, we'll use the experimental/meta library.

So plainly stated, what we intend to do is generate code equivalent to the following:

namespace a {
  int foo() { return 0; }
}

namespace b {
  constexpr int foo() { return 0; }
}

Since we need to make modifications to our reflection, the injection declaration is poorly suited. Instead, we'll use a metaprogram declaration, and an injection statement.

namespace a {
  int foo() { return 0; }
}

namespace b {
  consteval {
    meta::info foo_reflection = reflexpr(a::foo);
    meta::make_constexpr(foo_reflection);
    -> foo_reflection;
  }
}

While it may seem obvious, it's important to note, multiple modifications can be applied to the same reflection. For instance, let's say namespace b follows a convention where all constexpr functions have a constexpr_ prefix.

We want code equivalent to the following:

namespace a {
  int foo() { return 0; }
}

namespace b {
  constexpr int constexpr_foo() { return 0; }
}

We can easily achieve this by using a combination of the __concatenate intrinsic and a few experimental/meta functions.

namespace a {
  int foo() { return 0; }
}

namespace b {
  consteval {
    meta::info foo_reflection = reflexpr(a::foo);
    meta::make_constexpr(foo_reflection);

    const char* existing_name = meta::get_name(foo_reflection);
    meta::set_new_name(foo_reflection, __concatenate("constexpr_", existing_name));

    -> foo_reflection;
  }
}

Modified Declaration Cloning with Iteration

Making a copy of single declaration with a few modifications is neat, and allows our code base to be more DRY. However, maybe there are many functions inside of namespace a that we want to make constexpr, and rename.

So we want to take a namespace that looks like the following:

namespace a {
  double pi = 3.14;

  int foo_1() { return 1; }
  int foo_2() { return 2; }
  ...
  int foo_n() { return n; }
}

Then generate namespace b, where all the functions inside of namespace a are cloned, and made constexpr, but only the functions.

So we want code equivalent to the following:

namespace a {
  double pi = 3.14;

  int foo_1() { return 1; }
  int foo_2() { return 2; }
  ...
  int foo_n() { return n; }
}

namespace b {
  constexpr int constexpr_foo_1() { return 1; }
  constexpr int constexpr_foo_2() { return 2; }
  ...
  constexpr int constexpr_foo_n() { return n; }
}

We can achieve that as follows:

namespace a {
  double pi = 3.14;

  int foo_1() { return 1; }
  int foo_2() { return 2; }
  ...
  int foo_n() { return n; }
}

namespace b {
  consteval {
    meta::member_range namespace_a_range(reflexpr(a));
    for (meta::info element : namespace_a_range) {
      if (!meta::is_function(element))
        continue;

      meta::make_constexpr(element);

      const char* existing_name = meta::get_name(element);
      meta::set_new_name(element, __concatenate("constexpr_", existing_name));

      -> element;
    }
  }
}

Fragment Injection

Basic Fragment Injection

Fragment injection is very similar to declaration cloning. However, there are a few key differences. Namely, fragments do not support modifiers, and fragments inject their enclosed contents. That is to say that, for instance, when you inject a class fragment, you are not injecting a class, but, you are instead injecting the members defined in that class fragment.

Let's say we want to create a metaprogram that will generate code equivalent to the following:

class a {
public:
  int foo() { return 0; }
};

We can do that simply using an injection declaration as follows:

class a {
  consteval -> fragment class {
  public:
    int foo() { return 0; }
  };
};

It's worth noting, we could also choose to implement this slightly differently using a struct fragment which implicitly gives public access to its members:

class a {
  consteval -> fragment struct {
    int foo() { return 0; }
  };
};

Fragment Injection with Captures

We can also write the previously example slightly differently using a capture. To do this, we will need a metaprogram declaration, paired with an injection statement. We will also need to use the the unquote operator to reference and captured our variable.

struct a {
  consteval {
    int my_captured_return_value = 0;

    -> fragment struct {
      int foo() { return %{my_captured_return_value}; }
    };
  }
};

At the moment, there is no way to specify alternative capture modes, and all captures are by value.

Fragment Injection with Captures and Iteration

Similar to iteration with declaration cloning, we can perform iteration with fragment injection.

For instance, if we wanted to recreate namespace b from the Declaration Cloning with Iteration example:

namespace b {
  constexpr int constexpr_foo_1() { return 1; }
  constexpr int constexpr_foo_2() { return 2; }
  ...
  constexpr int constexpr_foo_n() { return n; }
}

We can do that as follows. Let's say given an n value of 200:

namespace b {
  consteval {
    const char* base_name = "constexpr_foo_";

    for (int i = 1; i <= 200; ++i) {
      -> fragment namespace {
        constexpr int unqualid(%{base_name}, %{i})() {
          return %{i};
        }
      };
    }
  }
}

Nested Metaprograms

With more complex metaprograms, it may become useful to use nesting, perhaps even to the point where it becomes necessary as a matter of practicality to keep a code base maintainable.

Let's say our application architecture is a metaprogramming based component system. That's to say we have some entities, composed of various components. Our components are setup via fragments which include a data member (the respective component object), and some delegating methods.

The choice to use metaprogramming was based on a decision to eliminate boilerplate, and reduce opportunities for developers to make small mistakes that cause large problems.

Our code base currently looks something like what follows:

class entity_a {
  consteval -> position_component_fragment;
  consteval -> acceleration_component_fragment;
  consteval -> inventory_component_fragment;

  void adjust_velocity(...);
  ...
};

class entity_b {
  consteval -> position_component_fragment;
  consteval -> acceleration_component_fragment;
  consteval -> stopwatch_component_fragment;

  void adjust_velocity(...);
  ...
};

...

Since adopting metaprograms for setting up our components, we've noticed a reduction in errors, however, our developers frequently intend to make an entity conform to our moveable concept, which is composed of both the methods defined by the position, and acceleration components.

Many of our entities also implement a duplicated function for adjusting their velocity.

So, we've decided to remedy this by creating a new moveable fragment using nested injection.

constexpr moveable_composite_component_fragment = fragment class {
  consteval -> %{position_component_fragment};
  consteval -> %{acceleration_component_fragment};

  void adjust_velocity(...);
};

This allows our developers to rewrite our existing entities more simply, and create new entities with slightly less code, giving a reduction in our hypothetical development time. It also gives us a new place for the duplicated adjust_velocity function, helping us with our goal of DRYing up the code.

Our codebase is now structured as follows:

class entity_a {
  consteval -> moveable_composite_component_fragment;
  consteval -> inventory_component_fragment;
  ...
};

class entity_b {
  consteval -> moveable_composite_component_fragment;
  consteval -> stopwatch_component_fragment;
  ...
};

...

Metaclasses

Another practical case for metaprograms is helping us implement patterns we're already used to, faster.

The Metaprogram Declaration Way

Let's say you regularly are writing models for a database that look somewhat similar to the following model:

class book_model {
  std::string author_name;
  int page_count;

public:
  book_model(const std::string& author_name, const int& page_count)
    : author_name(author_name), page_count(page_count) { }

  void set_author_name(const std::string& author_name) {
    this->author_name = author_name;
  }

  std::string get_author_name() const {
    return author_name;
  }

  void set_page_count(const int& page_count) {
    this->page_count = page_count;
  }

  int get_page_count() const {
    return page_count;
  }
};

There is a tremendous amount of boiler plate presented here. With tools already presented we can write a metaprogram to simplify this:

consteval void gen_member(meta::info member) {
  -> fragment struct {
    void unqualid("set_", meta::name_of(%{member}))(const typename(meta::type_of(%{member}))& unqualid(meta::name_of(%{member}))) {
      this->unqualid(meta::name_of(%{member})) = unqualid(meta::name_of(%{member}));
    }
  };

  -> fragment struct {
    typename(meta::type_of(%{member})) unqualid("get_", meta::name_of(%{member}))() {
      return unqualid(meta::name_of(%{member}));
    }
  };
}

consteval void gen_members(meta::info clazz) {
  for (meta::info member : meta::data_member_range(clazz)) {
    gen_member(member);
  }
}

class book_model {
  std::string author_name;
  int page_count;

public:
  book_model(const std::string& author_name, const int& page_count)
    : author_name(author_name), page_count(page_count) { }

  consteval {
    gen_members(reflexpr(book_model));
  }
};

However, this leaves opportunity for errors. The user can reflect on the wrong class, and inject getters and setters for another class. In this particular example, this would likely be caught by the compiler as there would have to be data members of equivalent type and name. However, even in this best case scenario, that's wasted development time.

There are other avoidable errors that occur with this approach as well. Let's say we added a method to the book_model, and for simplicity's sake, we updated gen_members to have an else case that simply injects the member:

consteval void gen_members(meta::info clazz) {
  for (meta::info member : meta::member_range(clazz)) {
    if (meta::is_data_member(member))
      gen_member(member);
    else
      -> member;
  }
}

This code may seem innocent enough, however, it will result in a compile time infinite loop. The metaprogram declaration will be picked up in our loop over the members of clazz, injected then run, again, and again.

The Metaclass Way

Thus we have metaclasses, which both reduce the amount of code that needs written, and make it quite difficult to run into these problems.

The following code is equivalent to our previous working example, except using a metaclass:

consteval void gen_member(meta::info member) {
  -> fragment struct {
    void unqualid("set_", meta::name_of(%{member}))(const typename(meta::type_of(%{member}))& unqualid(meta::name_of(%{member}))) {
      this->unqualid(meta::name_of(%{member})) = unqualid(meta::name_of(%{member}));
    }
  };

  -> fragment struct {
    typename(meta::type_of(%{member})) unqualid("get_", meta::name_of(%{member}))() {
      return unqualid(meta::name_of(%{member}));
    }
  };
}

consteval void model(meta::info clazz) {
  for (meta::info member : meta::member_range(clazz)) {
    -> member;

    if (meta::is_data_member(member))
      gen_member(member);
  }
}

class(model) book_model {
  std::string author_name;
  int page_count;

public:
  book_model(const std::string& author_name, const int& page_count)
    : author_name(author_name), page_count(page_count) { }
};

You can see a few minor but significant changes. It's no longer possible to apply the meta program to the wrong class, as the meta program is specified as part of the class declaration itself.

Additionally, there is no longer the need for a metaprogram declaration in the class definition. Instead, the metaclass is specified in the declaration via class(metafunction-name) where metafunction-name is the name of a constexpr compatible function, that when used to form a metaclass, we call a metafunction. This metafunction eliminates the possibility of having our metafunction accidentally inject itself resulting in an infinite loop.

An arguable simplicity is also introduced, as additionally, the only members that will remain in the class, are specifically the members you inject.

Transformative Properties

Building on our previous example, it may be noted, that since only the injected members remain in the class after application of the metaclass, this provides the opportunity for metaclasses to take existing content in the class, and replace it.

This can be useful in many cases. One example is in attempting to modernize legacy applications. Let's say that our code base has many legacy "models" which use unnecessary suffixes (e.g. "_str" for std::string, "_int" for int, etc). We can create a metaclass that allows us to write modern code, that gets converted to our legacy structure to maintain binary compatibility.

Take the following legacy source:

class book_model {
  std::string author_name_str;
  int page_count_int;

public:
  book_model(const std::string& author_name_str, const int& page_count_int)
    : author_name(author_name_str), page_count(page_count_int) { }

  void set_author_name_str(const std::string& author_name_str) {
    this->author_name_str = author_name_str;
  }

  std::string get_author_name_str() const {
    return author_name_str;
  }

  void set_page_count_int(const int& page_count_int) {
    this->page_count_int = page_count_int;
  }

  int get_page_count_int() const {
    return page_count_int;
  }
}

We can create the following metaclass which is a slight modification of our previous metaclass:

consteval const char* get_legacy_suffix(meta::info member) {
  meta::info member_type = meta::type_of(member);

  if (member_type == reflexpr(std::string))
    return "_str";
  else if (member_type == reflexpr(int))
    return "_int";

  return "";
}

consteval const char* get_legacy_name(meta::info member) {
  return __concatenate(meta::name_of(member), get_legacy_suffix(member));
}

consteval void gen_legacy_member(meta::info member) {
  meta::set_new_name(member, get_legacy_name(member));
  -> member;

  -> fragment struct {
    void unqualid("set_", meta::name_of(%{member}))(const typename(meta::type_of(%{member}))& unqualid(meta::name_of(%{member}))) {
      this->unqualid(meta::name_of(%{member})) = unqualid(meta::name_of(%{member}));
    }
  };

  -> fragment struct {
    typename(meta::type_of(%{member})) unqualid("get_", meta::name_of(%{member}))() {
      return unqualid(meta::name_of(%{member}));
    }
  };
}

consteval void legacy_model(meta::info clazz) {
  for (meta::info member : meta::member_range(clazz)) {
    if (meta::is_data_member(member)) {
      gen_legacy_member(member);
      continue;
    }

    -> member;
  }
}

class(legacy_model) book_model {
  std::string author_name;
  int page_count;

public:
  book_model(const std::string& author_name, const int& page_count)
    : author_name(author_name), page_count(page_count) { }
};

If we wanted to take this a step further, and allow new code to use member functions without the suffix, we could modify our original gen_members function, making a gen_modern_members_for_legacy_member function:

consteval void gen_modern_members_for_legacy_member(meta::info member) {
  -> fragment struct {
    void unqualid("set_", meta::name_of(%{member}))(const typename(meta::type_of(%{member}))& unqualid(meta::name_of(%{member}))) {
      this->unqualid(get_legacy_name(%{member})) = unqualid(meta::name_of(%{member}));
    }
  };

  -> fragment struct {
    typename(meta::type_of(%{member})) unqualid("get_", meta::name_of(%{member}))() {
      return unqualid(get_legacy_name(%{member}));
    }
  };
}

Then to use it, we change our legacy_model metafunction slightly:

consteval void legacy_model(meta::info clazz) {
  for (meta::info member : meta::member_range(clazz)) {
    if (meta::is_data_member(member)) {
      gen_legacy_member(member);
      // Call to our new function, so that we generate compatible modern members
      gen_modern_members_for_legacy_member(member);
      continue;
    }

    -> member;
  }
}

This puts us in a great situation going forward, as we can deprecate the legacy logic. Once sufficient time has passed and all code has been adapted to the new pattern, we simply change legacy_model to model, and all the legacy generation is removed, with no binary incompatibilities introduced.

Type Transformers

Type transformers provide a facility very similar to metaclasses, like, a metaclass, they form a metaclass via a metafunction, however, unlike a metaclass, both the source class, and the metaclass will coexist.

Type transformers have the following syntax:

using class identifier-name as metafunction-name(type-reflection);

The identifier-name is the name of the new metaclass type you're generating. The metafunction-name similar to the case with metaclasses is the name of the metafunction that will be used to generate the metaclass, and the type-reflection is a reflection of the class type which you want to use as a source.

Type transformers are particularly useful when you want to transform an existing class that you don't have the ability to modify. For example, perhaps you want to use a third party library, but the third party library uses an unfavorable coding style.

Using a type transformer, you could write a wrapper class that could be used in your code base, with minimal maintenance burden, while at the same time, via implicit conversion operators, would be interoperable with the library's existing functions.

Parameter Injection

Sometimes it becomes useful to be able to clone parameters from one function, into another. For example, if we wanted to create a metafunction for integration into a simple timings system, we can do that using parameter injection.

Let's say our existing code looks as follows:

class bar {
  void foo(int a, int b, int c) {
    ...
  }

  ...
};

We want to rework this code to integrate with our timings system:

class bar {
  void foo(int a, int b, int c) {
    begin_timing("bar::foo");
    ...
    end_timing("bar::foo");
  }

  ...
};

We can write a meta function, and make bar a metaclass, so that we can change this transformation from a change that's potentially an impractical number of lines, to a single line change.

Said meta function could look similar to the following -- though this is notably oversimplified for demonstration purposes, and an implementation that could truly handle any class would be more complex:

consteval void timings(meta::info source) {
  for (meta::info member : meta::member_range(source)) {
    if (meta::is_member_function(member)) {
      meta::info member_copy = member;
      meta::make_private(member_copy);
      meta::set_new_name(member_copy, __concatenate(meta::name_of(member), "_impl"));
      -> member_copy;

      meta::param_range params(member);
      -> fragment struct {
        void unqualid(meta::name_of(%{member}))(-> %{params}) {
          begin_timing(__concatenate(meta::name_of(%{source}), meta::name_of(%{member})));
          unqualid(meta::name_of(%{member}), "_impl")(unqualid(... %{params}));
          end_timing(__concatenate(meta::name_of(%{source}), meta::name_of(%{member})));
        }
      };
      continue;
    }

    -> member;
  }
}

This makes use of both parameter injection, and variadic unqualid to call out to the original implementation forwarding all arguments, while wrapping the function with the timings system.

This meta function could then be applied to any compatible existing class.

Fragment Requirements

While writing fragments, you sometimes want to depend on a named entity which will be found in the context being injected into.

Required Types

Required types are specified with the syntax:

requires typename identifier;

Where identifier is the identifier for a type-name to be resolved when this declaration is injected.

Prior to injection, inside the fragment preceding the required type declaration, identifier is a dependent type. At the point of injection the type-name is looked up. If a corresponding type is found, it's then substituted for all corresponding dependent types.

With this system, you can refer to a type that isn't fully defined yet, and provided the type is fully defined at the injection site, use it as if it were.

As a simple example, consider:

constexpr meta::info frag = fragment {
  requires typename Foo;

  Foo x;
  return x.val;
};

struct Foo {
  int val = 10;
};

int return_num() {
  consteval -> frag;
}

Required Declarators

Required declarators allow you to find declarations that have yet to be declared, and are specified with the syntax:

requires decl-specifier-seq declarator;

Where declarator is any valid C++ declarator, and decl-specifier-seq is a decl-specifier-seq of specifiers required to be on the declaration. This means that required declarations will only find declarations where both the declarator and its specifiers match.

As an example, consider the following three cases:

constexpr int foo();

consteval -> fragment namespace {
  requires constexpr int foo();
};
int foo();

consteval -> fragment namespace {
  requires constexpr int foo();
};
int foo();

consteval -> fragment namespace {
  requires int& foo();
};

In the first case, the required declarator will be resolved successfully, as both the declarator, and its specifies match.

In the second case, the required declarator will not be resolved successfully, as the specifiers, do not match. In particular, the required declaration is constexpr, but the found declaration is not.

In the third case, the required declarator will not be resolved successfully, as the return type of the declarator does not match the return type of the found declaration.

Requiring Functions

It's important to note that, a required declarator can only ever resolve to one declaration. Functions in C++ however, can be overloaded, resulting in multiple candidates. Required declarators use argument dependent lookup, to resolve these candidates and select a single function.

Take the following example:

void f() {}
void f(int a) {}

consteval -> fragment namespace {
  requires void f();
};

In this example, the function f's overload with no parameters will be used, and the f overload which accepts an int argument shall be ignored.

If we want to require both, we must require both explicitly, as follows:

void f() {}
void f(int a) {}

consteval -> fragment namespace {
  requires void f();
  requires void f(int a);
};

Additionally, as argument dependent lookup is applied, a required declarator with no arguments can find a declaration which accepts multiple arguments, as demonstrated in the following example:

int f(int a = 0, int b = 0) {}

consteval -> fragment namespace {
  requires int f();
};

It's important to note, however, this does not allow you to call the required function f with more arguments than you required. As an example, consider the following:

int f(int a = 0, int b = 0) {}

consteval -> fragment namespace {
  requires int f();

  int result = f(1);
};

This would result in an error, as the required f used inside of the fragment does not take any arguments, despite the found declaration accepting up to two arguments.

Namespace and Block Fragments

When a required declarator inside of a namespace, or block fragment is injected, it can find any declarator that can be looked up.

As an example, consider the following:

int foo();

class bar {
  int get_foo() {
    consteval -> fragment {
      requires int foo();
      return foo();
    };
  }
};

This will resolved successfully to the declaration of foo in global scope.

Class Fragments

Unlike with namespace and block fragments, a required declarator inside of a class fragment can only find members of the class its injected into -- as well as said class's respective bases.

Consider the following:

int foo();

class bar {
  consteval -> fragment class {
    requires int foo();
  };
};

In contrast to the example given for namespace and block fragment lookup, this will fail, as it is not allowed to look outside of the class, and find the declaration of foo in global scope.

Injection into Alternative Contexts

The injection statement, by default will inject into the current context of the metaprogram. However, in certain circumstances it is desirable to inject into other contexts.

Injection into the Parent Namespace

Injection into the parent namespace can be achieved by adding the namespace keyword, before the fragment or reflection being injected.

As an example, let's say we wanted to add a factory function to the enclosing namespace of our class, generating logic similar to the following:

class factory_instance {
  int value;

public:
  factory_instance(int v) : value(v) { }

  int get_value() { return value; }
  void set_value(int v) { this->value = v; }
};

factory_instance create_factory_instance() {
  return factory_instance(10);
}

To inject into the parent namespace, you must specify an injection context, for example:

-> namespace injection;

This comes together as follows:

class factory_instance {
  int value;

public:
  factory_instance(int v) : value(v) { }

  int get_value() { return value; }
  void set_value(int v) { this->value = v; }

  consteval -> namespace fragment namespace {
    requires typename factory_instance;

    factory_instance create_factory_instance() {
      return factory_instance(10);
    }
  }
};

Note the use of the required typename. This allows us to use the factory_instance type as if it is fully defined.

Injecting into the Specified Namespace

In some cases, it may be desirable to inject into a specific namespace.

As an example, let's say, you're creating a geometry library, and you want all areas function to live under the same namespace, but be defined with the class. As an example consider:

namespace {
  struct square {
    unsigned width, height;
  };

  namespace area {
    unsigned calculate(const square& s) {
      return s.width * s.height;
    }
  }
}

To inject into a specific namespace, you must specify an injection context, for example:

-> namespace(namespace-name) injection;

This comes together as follows:

namespace shape {
  namespace area { }

  struct square {
    unsigned width, height;

    consteval -> namespace(area) fragment namespace {
      requires typename square;

      unsigned calculate(const square& s) {
        return s.width * s.height;
      }
    }
  };
}

Order of Injection with Nesting

When all injections are destined for the same target, the order of injection is the order in which the injections are encountered, in a depth first manner.

Meaning, if we have an injection hierarchy similar to the following:

- Target Class
 \
  |- Injection A
  |- Injection B
  | \
  |  |- Injection C
  |
  |- Injection D

Or in words, given a situation where A, B, and D are injected into Target Class, where B has a nested injection C.

The order of injection will be A, B, C, D.

This situation changes slightly if one of the injections injects into a namespace. For instance, if B is an injection into a namespace, the hierarchy would then look as follows:

- Target Class
 \
  |- Injection A
  |- Injection D
- Injection B
 \
  |- Injection C

Or in words, given a situation where A, B, and D are injected into Target Class, where B is a namespace injection with a nested injection C.

The order of injection will be A, D, B, C.

This allows for the definition of Target Class to be completed, so that it can be used as a complete type in the namespace injection. This same pattern is followed even when Target Class is not a class for consistency.