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:
meta::info_blueprint. These are blueprints only of single entities that can be represented as meta::info.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:
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:
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.
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") :];
};
}
Because we use a single type for injection, we could add a powerful library API on top. This includes:
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.
info_blueprint after it was created with an unparsed block.info injection_context_of(info_blueprint) to query where we are injecting things (I think this can be very useful for fragments).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.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;
}
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))...);
}
};
}
info_blueprint, we could add powerful library meta-functions on top of it.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.
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 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