Date: Sun, 17 Aug 2025 14:36:47 +0300
The core of the idea is to use something similar to token sequences (which
is a great basis) but completely replace their composition mechanism with
something that is part of the language, and written *inside* the token
sequence. This is more of an aggregation, not composition.
Disclaimer: One core issue with my approach, forces us to reintroduce some
form of range splices back into the language. I have a separate idea on how
to make this work without reinventing pack expansions, which should be its
own proposal. For the sake of understanding the idea, assume range splices
are a thing.
The premise is that we maintain a symmetry between reflection and
injection. We introduce two things:
1. A mechanism to create "entities to be injected" which is similar to
token sequences, but different in several ways. Call it
*blueprint-expression*. The type of a blueprint expression is
meta::info_blueprint. These are blueprints *only* of single entities
that can be represented as meta::info.
2. A new kind of splice that injects a whole definition, based on a
blueprint. Call it *injection splice*.
Everywhere a reflectible entity can be defined in the grammar, this
"injection splice" would also be allowed.
The composition of blueprints would be achieved by using injection splices
*inside* the token sequence of the blueprint.
By having a single type for the blueprints, we can also query and edit them
"OOP style" after they were created and before they were injected.
A little more in-depth:
Blueprint expression
The mechanism to create the entities to be injected, is with a
*blueprint-expression*. It is very similar to a token sequence from P3294
but with a number of key differences:
1. Upon parsing the underlying token sequence (in the right context), it
must result in a single entity that is reflectible.
2. We don't allow concatenation.
3. The type of this expression is NOT meta::info. It must be something
else, call it meta::info_blueprint.
Think of this expression like defining a consteval templated lambda, whose
call operator parses the underlying token sequence and produces the single
entity we want to inject. The template parameters are encoding the context
needed by the parser to parse the underlying token sequence (what we are
parsing, and source location). The injection point is what defines these
template parameters.
In fact, we can add parameters (and maybe even captures) just like for a
lambda. We can use a lambda-like syntax for a *blueprint-expression*:
[](std::string_view name)^^{int \%(name);};
where inside the ^^{} block we use similar syntax to P3294 (here \% is a
name interpolator). When injecting this in block scope, this is parsed as a
local variable. At class scope, an NSDM. In a function parameter list, it
becomes a function parameter declaration.
Injection splice
The idea is that splicing a blueprint is the same as writing the
declaration in the location of the splice. This is very much like a splice,
only where the reflection object we are splicing is being created here.
We can't use splices directly for several reasons, and we need a
disambiguator. We can use the following syntax:
new declaration using [: value :]
where value is a constant expression convertible to info_blueprint and must
be *non-dependent*.
declaration and expression would be context-sensitive keywords that go in
the middle.
Note this syntax is open-ended: if we want to later add injection of more
things with this syntax, we could just add more context sensitive keywords
in the middle (like statement or fragment, or even add special cases for
places where the grammar is different, like lambda-captures).
I think these categories, along with the injection-splice location are
enough information for the parser to determine how to parse the underlying
token sequence. Maybe there are edge cases relating to vexing parse, but
these won't be new.
For example:
constexpr meta::info_blueprint named_int = [](string_view name)^^{int
/%(name);};
void foo(new declaration using [: named_int ("param1") :],
new declaration using [: named_int ("param2") :] )
{
new declaration using [: named_int("local") :];
class S {
new declaration using [: named_int("nsdm") :];
};
}
Library Design
Because we use a single type for injection, we could add a powerful library
API on top. This includes:
- Querying the future info object that will be created from the fragment
expression. Say with a function:
info future_info_of(const info_blueprint);
Note that defining this as a regular function is tricky, because we cannot
allow things like typename [: type_of(future_info_of(blueprint_expr)) :].
We can fix this by being clever, so let's just assume this works for now.
- We can enforce we are parsing something specific using the C++26 API
on the "future info", along with assertions (or even better, with contract
postconditions).
- Add OOP style setters on some of the properties of a info_blueprint
after it was created with an unparsed block.
- Add a metafunction info injection_context_of(info_blueprint) to query
where we are injecting things (I think this can be very useful for
fragments).
- We can introduce a conversion from info into a info_blueprint as an
alternative way to create blueprints. The conversion maintains all the
semantic properties, but loses context information (that is parent_of,
source_location, linkage etc). This can be useful for example for
cloning a list of function parameters.
Examples
- Here is how we inject a member function, with a given list of params,
while making sure the name doesn't already exist. If it is, we prepend a
prefix to the name.
consteval meta::info_blueprint inject_only_to_class(string_view name,
vector<meta::info> params){
auto foo_blueprint = [&]()^^ {
int \%(name)(typename [: ...params :]...) {
return 42;
};
};
assert(meta::is_function(future_info_of(foo_bleuprint))); //make
sure this is a function
meta::info ctx = meta::injection_context_of(foo_blueprint);
assert(meta::is_type(ctx)); //We inject only to a class
//check if the name already exists
vector other_members = members_of(ctx);
auto found_name = ranges::find(other_members, name, [](info r){
return meta::identifier_of(r);});
if (found_name != other_member.end())
{
meta::set_name(foo_blueprint, string("some_prefix_") + name);
}
// the returned blueprint conditionaly have a modified name
return foo_blueprint;
}
- This is the example from 4.11 of P3294R2 written with this approach:
consteval auto add_first_param(info fun)
{
constexpr auto param_decl = [](info func_param)^^{ typename [:
type_of(func_param) :] \%(identifier_of(func_param));};
constexpr vector<meta::info> params = parameters_of(fun);
return [&]^^{
auto \%(identifier_of(fun))(new decleration using [:
param_decl(...params)]...)
-> typename [:return_type_of(fun) :]
{
return vtable->[: fun :](data, \%(identifier_of(...params))...);
}
};
}
Alternatively, with a library API for cloning, we don't even have to define
an intermediate blueprint (and we could preserve more complicated semantic
properties like default parameters):
consteval auto add_first_param(info fun)
{
constexpr vector<meta::info> params = parameters_of(fun);
constexpr vector<meta::info_blueprint> new_params(parmas.begin(),
params.end()); // using narrowing conversion on each element
return [&]^^{
auto \%(identifier_of(fun))(new declaration using [: ...new_params]...)
-> typename [:return_type_of(fun) :]
{
return vtable->[: fun :](data,
\%(identifier_of(...new_params))...);
}
};
}
Pros
1. It is structured and can be a building block for more high-level
injection mechanisms.
2. The composition is described using splices, and nesting blueprints
which look like lambdas which are familiar.
3. This forces users of injection to NOT pass the injection objects as
NTTPs. This means they will have to use value based meta-programming to
build them, not TMP (which is the preferred direction of the committee).
4. Because we use a single type info_blueprint, we could add powerful
library meta-functions on top of it.
Cons
1.
To write complex blueprints, we need to nest lambdas. This can be
intimidating to the reader (and look like callback hell even though it is
not). I think this part can be solved by a companion high-level mechanism
like fragments.
2.
We must make info_blueprint not usable as NTTP (like a lambda), and
force the injection splice operand to not be dependent. This means we lose
the power of TMP. Here specifically, we can't do an injection-splice on a
pack of values. We currently don't have range splices, this limits the
usage of this feature.
On Sat, Aug 16, 2025 at 7:14 PM Jens Maurer <jens.maurer_at_[hidden]> wrote:
>
>
> On 16.08.25 13:49, Omer Rosler via Std-Proposals wrote:
> > Hello all,
> >
> > I want to help make injection happen for C++29 and would really
> appreciate some context and history before delving in.
> > I read online that the favoured approach today is of token sequences
> from P3294R2 <http://wg21.link/P3294R2>, instead of fragments
> from P2237R0 <http://wg21.link/P2237R0>.
> >
> > I understand the token sequences approach is very powerful, but it is
> not ideal due to it being unstructured.
> >
> > What happened to the fragments approach? It looked very promising. I
> found only two technical discussions on the subject:
> > 1. The comparison part of P3294R3. Specifically the problem described in
> section 4.11.
> > 2. Discussions <https://lists.isocpp.org/sg7/2020/10/0042.php> on the
> SG7 reflector from 2020 about P2237 (in which it seems fragments is
> prefered).
> >
> > Note I am not on the committee yet, I don't have access to the wiki.
> >
> > From my understanding, the main technical problem with fragments is "the
> composition of fragments is awkward because we can only inject valid
> fragments".
>
> Right, and that's a major problem. C++ has a lot of syntax. The
> inspection API
> we already have is fairly large, and we'd need an even bigger API surface
> for
> doing fragment injection, it seems.
>
> > If this is the only major issue, I think it can be solved without
> abandoning the approach completely. I can elaborate if desired (and write a
> paper if this is deemed valuable).
>
> Care to elaborate the main idea here before investing into a paper?
>
> Thanks,
> Jens
>
>
is a great basis) but completely replace their composition mechanism with
something that is part of the language, and written *inside* the token
sequence. This is more of an aggregation, not composition.
Disclaimer: One core issue with my approach, forces us to reintroduce some
form of range splices back into the language. I have a separate idea on how
to make this work without reinventing pack expansions, which should be its
own proposal. For the sake of understanding the idea, assume range splices
are a thing.
The premise is that we maintain a symmetry between reflection and
injection. We introduce two things:
1. A mechanism to create "entities to be injected" which is similar to
token sequences, but different in several ways. Call it
*blueprint-expression*. The type of a blueprint expression is
meta::info_blueprint. These are blueprints *only* of single entities
that can be represented as meta::info.
2. A new kind of splice that injects a whole definition, based on a
blueprint. Call it *injection splice*.
Everywhere a reflectible entity can be defined in the grammar, this
"injection splice" would also be allowed.
The composition of blueprints would be achieved by using injection splices
*inside* the token sequence of the blueprint.
By having a single type for the blueprints, we can also query and edit them
"OOP style" after they were created and before they were injected.
A little more in-depth:
Blueprint expression
The mechanism to create the entities to be injected, is with a
*blueprint-expression*. It is very similar to a token sequence from P3294
but with a number of key differences:
1. Upon parsing the underlying token sequence (in the right context), it
must result in a single entity that is reflectible.
2. We don't allow concatenation.
3. The type of this expression is NOT meta::info. It must be something
else, call it meta::info_blueprint.
Think of this expression like defining a consteval templated lambda, whose
call operator parses the underlying token sequence and produces the single
entity we want to inject. The template parameters are encoding the context
needed by the parser to parse the underlying token sequence (what we are
parsing, and source location). The injection point is what defines these
template parameters.
In fact, we can add parameters (and maybe even captures) just like for a
lambda. We can use a lambda-like syntax for a *blueprint-expression*:
[](std::string_view name)^^{int \%(name);};
where inside the ^^{} block we use similar syntax to P3294 (here \% is a
name interpolator). When injecting this in block scope, this is parsed as a
local variable. At class scope, an NSDM. In a function parameter list, it
becomes a function parameter declaration.
Injection splice
The idea is that splicing a blueprint is the same as writing the
declaration in the location of the splice. This is very much like a splice,
only where the reflection object we are splicing is being created here.
We can't use splices directly for several reasons, and we need a
disambiguator. We can use the following syntax:
new declaration using [: value :]
where value is a constant expression convertible to info_blueprint and must
be *non-dependent*.
declaration and expression would be context-sensitive keywords that go in
the middle.
Note this syntax is open-ended: if we want to later add injection of more
things with this syntax, we could just add more context sensitive keywords
in the middle (like statement or fragment, or even add special cases for
places where the grammar is different, like lambda-captures).
I think these categories, along with the injection-splice location are
enough information for the parser to determine how to parse the underlying
token sequence. Maybe there are edge cases relating to vexing parse, but
these won't be new.
For example:
constexpr meta::info_blueprint named_int = [](string_view name)^^{int
/%(name);};
void foo(new declaration using [: named_int ("param1") :],
new declaration using [: named_int ("param2") :] )
{
new declaration using [: named_int("local") :];
class S {
new declaration using [: named_int("nsdm") :];
};
}
Library Design
Because we use a single type for injection, we could add a powerful library
API on top. This includes:
- Querying the future info object that will be created from the fragment
expression. Say with a function:
info future_info_of(const info_blueprint);
Note that defining this as a regular function is tricky, because we cannot
allow things like typename [: type_of(future_info_of(blueprint_expr)) :].
We can fix this by being clever, so let's just assume this works for now.
- We can enforce we are parsing something specific using the C++26 API
on the "future info", along with assertions (or even better, with contract
postconditions).
- Add OOP style setters on some of the properties of a info_blueprint
after it was created with an unparsed block.
- Add a metafunction info injection_context_of(info_blueprint) to query
where we are injecting things (I think this can be very useful for
fragments).
- We can introduce a conversion from info into a info_blueprint as an
alternative way to create blueprints. The conversion maintains all the
semantic properties, but loses context information (that is parent_of,
source_location, linkage etc). This can be useful for example for
cloning a list of function parameters.
Examples
- Here is how we inject a member function, with a given list of params,
while making sure the name doesn't already exist. If it is, we prepend a
prefix to the name.
consteval meta::info_blueprint inject_only_to_class(string_view name,
vector<meta::info> params){
auto foo_blueprint = [&]()^^ {
int \%(name)(typename [: ...params :]...) {
return 42;
};
};
assert(meta::is_function(future_info_of(foo_bleuprint))); //make
sure this is a function
meta::info ctx = meta::injection_context_of(foo_blueprint);
assert(meta::is_type(ctx)); //We inject only to a class
//check if the name already exists
vector other_members = members_of(ctx);
auto found_name = ranges::find(other_members, name, [](info r){
return meta::identifier_of(r);});
if (found_name != other_member.end())
{
meta::set_name(foo_blueprint, string("some_prefix_") + name);
}
// the returned blueprint conditionaly have a modified name
return foo_blueprint;
}
- This is the example from 4.11 of P3294R2 written with this approach:
consteval auto add_first_param(info fun)
{
constexpr auto param_decl = [](info func_param)^^{ typename [:
type_of(func_param) :] \%(identifier_of(func_param));};
constexpr vector<meta::info> params = parameters_of(fun);
return [&]^^{
auto \%(identifier_of(fun))(new decleration using [:
param_decl(...params)]...)
-> typename [:return_type_of(fun) :]
{
return vtable->[: fun :](data, \%(identifier_of(...params))...);
}
};
}
Alternatively, with a library API for cloning, we don't even have to define
an intermediate blueprint (and we could preserve more complicated semantic
properties like default parameters):
consteval auto add_first_param(info fun)
{
constexpr vector<meta::info> params = parameters_of(fun);
constexpr vector<meta::info_blueprint> new_params(parmas.begin(),
params.end()); // using narrowing conversion on each element
return [&]^^{
auto \%(identifier_of(fun))(new declaration using [: ...new_params]...)
-> typename [:return_type_of(fun) :]
{
return vtable->[: fun :](data,
\%(identifier_of(...new_params))...);
}
};
}
Pros
1. It is structured and can be a building block for more high-level
injection mechanisms.
2. The composition is described using splices, and nesting blueprints
which look like lambdas which are familiar.
3. This forces users of injection to NOT pass the injection objects as
NTTPs. This means they will have to use value based meta-programming to
build them, not TMP (which is the preferred direction of the committee).
4. Because we use a single type info_blueprint, we could add powerful
library meta-functions on top of it.
Cons
1.
To write complex blueprints, we need to nest lambdas. This can be
intimidating to the reader (and look like callback hell even though it is
not). I think this part can be solved by a companion high-level mechanism
like fragments.
2.
We must make info_blueprint not usable as NTTP (like a lambda), and
force the injection splice operand to not be dependent. This means we lose
the power of TMP. Here specifically, we can't do an injection-splice on a
pack of values. We currently don't have range splices, this limits the
usage of this feature.
On Sat, Aug 16, 2025 at 7:14 PM Jens Maurer <jens.maurer_at_[hidden]> wrote:
>
>
> On 16.08.25 13:49, Omer Rosler via Std-Proposals wrote:
> > Hello all,
> >
> > I want to help make injection happen for C++29 and would really
> appreciate some context and history before delving in.
> > I read online that the favoured approach today is of token sequences
> from P3294R2 <http://wg21.link/P3294R2>, instead of fragments
> from P2237R0 <http://wg21.link/P2237R0>.
> >
> > I understand the token sequences approach is very powerful, but it is
> not ideal due to it being unstructured.
> >
> > What happened to the fragments approach? It looked very promising. I
> found only two technical discussions on the subject:
> > 1. The comparison part of P3294R3. Specifically the problem described in
> section 4.11.
> > 2. Discussions <https://lists.isocpp.org/sg7/2020/10/0042.php> on the
> SG7 reflector from 2020 about P2237 (in which it seems fragments is
> prefered).
> >
> > Note I am not on the committee yet, I don't have access to the wiki.
> >
> > From my understanding, the main technical problem with fragments is "the
> composition of fragments is awkward because we can only inject valid
> fragments".
>
> Right, and that's a major problem. C++ has a lot of syntax. The
> inspection API
> we already have is fairly large, and we'd need an even bigger API surface
> for
> doing fragment injection, it seems.
>
> > If this is the only major issue, I think it can be solved without
> abandoning the approach completely. I can elaborate if desired (and write a
> paper if this is deemed valuable).
>
> Care to elaborate the main idea here before investing into a paper?
>
> Thanks,
> Jens
>
>
Received on 2025-08-17 11:36:59