Date: Sun, 21 Sep 2025 15:11:04 -0700
Hello,
Reflection has been mentioned as replacing external code generation tools
and thus simplifying development. There is potential for reflection to
replace external static analysis tools as well; potentially even moving
what would be core language safety features into libraries instead.
tl;dr: we can do a little bit of linting with current reflection; if we
could reflect on statically-known object lifetimes, we could implement
safety checks on a per-type basis to minimize safety errors on actually
safe and correct code.
This email is more exploring a design space than me proposing concrete
features that I want.
With reflection features on classes, we can implement something similar to
some uses of concepts.
```C++
class cat : public mylib::BaseAnimal {
int paws = 4;
}
static_assert(myLib::implementsAnimalCorrectly(^^cat));
```
where `myLib::implementsAnimalCorrectly` iterates over the passed class
and checks that the required fields, perhaps ones that BaseAnimal doesn't
already contain. Unlike concepts, myLib::implementsAnimalCorrectly could
enforce naming conventions, like having all member fields that are
prefixed with `m_`.
If there was an attribute to get the compiler to automatically call
`static_assert(myLib::implementsAnimalCorrectly(^^$class));` on all child
classes of mylib::BaseAnimal, then the consumers of BaseAnimal wouldn't
need to remember to run the static_assert to have correct code. (There
likely should be another attribute to disable it, in case of a buggy
library or the user wants to do something dangerous)
This example doesn't seem particularly useful to me, but it demonstrates
the idea of using reflection to do linting and similar static analysis,
and registering a checking function with the compiler to automatically run
the checks.
If we had a trigger to run an consteval function at compile time for every
accesses your class's fields, then you could implement ersatz-private
fields, or protected-fields that use a different logic than inheritance to
control access. This also don't seem particularly useful to me, but I
mention it to show how core language features could be implemented with
reflection.
# The key part of this email
As for a potential actually useful feature, the thing I am most interested
in, is if we figured out how to offer reflection over statically-known
lifetimes of objects and owner-pointer relationships and had corresponding
triggers, then libraries could off safety checks closer to their actual
semantics, instead of the C++ standard having to decide on heuristics for
safety.
Rust's "unlimited constant references or one mutable reference" guarantees
safety (like preventing iterator invalidation), but it has many false
positives by erroring on completely safe code. By allowing data type
implementors to define custom code to do the checking, instead of one set
of rules which everyone must structure their code to match, we can permit
a maximum number of safe usages while still catching the errors.
For example, a `std::vector` implementer can allow people to modify
elements in place while iterating over it. A very clever `std::vector`
implementer might be able to allow `push_back` of new elements while
iterating over it if they can statically tell there is enough space
already reserved.
The library implementers could potentially statically check errors which
aren't memory safety issues, like internet protocol library which warns
when an object statically known to contain the private key is passed to
the "transmitToRemoteServer" function.
There are lots of challenges for designing how introspection over object
lifetimes. Ideally, the reflection should be able to see as far as the
compiler can even if the compiler can't consistently see that far. For
example, inside function call, if the compiler happens to know the
definition of that function. However, that makes writing safety checking
code very hard or impossible because safety often requires showing that
something doesn't happen.
Likewise, implementation could be challenging since lifetime information
is known late in the compilation process, perhaps after implementations
currently run constexpr and consteval functions.
Primarily, I write this email because I haven't seem this potential of
reflection be mentioned yet in my reading or listening to CPPcon talks.
Libraries being able to easily write static checks to make sure you are
using them correctly seems very useful, and it is in the two intersection
of improving safety and reflection, both of which lots of design work is
being done on currently. So keeping this possibility in mind when doing
such design work might be useful.
Thank you for reading my thoughts..
Emily Ammundsen
P.S. if we can reflect over namespaces, then adding a single
`static_assert` at the end of a compilation unit could do linting for
naming conventions by recusing over all the visible names. If there was a
`static_assert_delay` which ran a static_assert as if it was at the end of
the compilation unit, then a header included at the beginning could
enforce naming conventions. With sufficiently powerful reflection
abilities, we don't need the triggers since we can just iterate over all
the code and catch the accesses we care about.
Reflection has been mentioned as replacing external code generation tools
and thus simplifying development. There is potential for reflection to
replace external static analysis tools as well; potentially even moving
what would be core language safety features into libraries instead.
tl;dr: we can do a little bit of linting with current reflection; if we
could reflect on statically-known object lifetimes, we could implement
safety checks on a per-type basis to minimize safety errors on actually
safe and correct code.
This email is more exploring a design space than me proposing concrete
features that I want.
With reflection features on classes, we can implement something similar to
some uses of concepts.
```C++
class cat : public mylib::BaseAnimal {
int paws = 4;
}
static_assert(myLib::implementsAnimalCorrectly(^^cat));
```
where `myLib::implementsAnimalCorrectly` iterates over the passed class
and checks that the required fields, perhaps ones that BaseAnimal doesn't
already contain. Unlike concepts, myLib::implementsAnimalCorrectly could
enforce naming conventions, like having all member fields that are
prefixed with `m_`.
If there was an attribute to get the compiler to automatically call
`static_assert(myLib::implementsAnimalCorrectly(^^$class));` on all child
classes of mylib::BaseAnimal, then the consumers of BaseAnimal wouldn't
need to remember to run the static_assert to have correct code. (There
likely should be another attribute to disable it, in case of a buggy
library or the user wants to do something dangerous)
This example doesn't seem particularly useful to me, but it demonstrates
the idea of using reflection to do linting and similar static analysis,
and registering a checking function with the compiler to automatically run
the checks.
If we had a trigger to run an consteval function at compile time for every
accesses your class's fields, then you could implement ersatz-private
fields, or protected-fields that use a different logic than inheritance to
control access. This also don't seem particularly useful to me, but I
mention it to show how core language features could be implemented with
reflection.
# The key part of this email
As for a potential actually useful feature, the thing I am most interested
in, is if we figured out how to offer reflection over statically-known
lifetimes of objects and owner-pointer relationships and had corresponding
triggers, then libraries could off safety checks closer to their actual
semantics, instead of the C++ standard having to decide on heuristics for
safety.
Rust's "unlimited constant references or one mutable reference" guarantees
safety (like preventing iterator invalidation), but it has many false
positives by erroring on completely safe code. By allowing data type
implementors to define custom code to do the checking, instead of one set
of rules which everyone must structure their code to match, we can permit
a maximum number of safe usages while still catching the errors.
For example, a `std::vector` implementer can allow people to modify
elements in place while iterating over it. A very clever `std::vector`
implementer might be able to allow `push_back` of new elements while
iterating over it if they can statically tell there is enough space
already reserved.
The library implementers could potentially statically check errors which
aren't memory safety issues, like internet protocol library which warns
when an object statically known to contain the private key is passed to
the "transmitToRemoteServer" function.
There are lots of challenges for designing how introspection over object
lifetimes. Ideally, the reflection should be able to see as far as the
compiler can even if the compiler can't consistently see that far. For
example, inside function call, if the compiler happens to know the
definition of that function. However, that makes writing safety checking
code very hard or impossible because safety often requires showing that
something doesn't happen.
Likewise, implementation could be challenging since lifetime information
is known late in the compilation process, perhaps after implementations
currently run constexpr and consteval functions.
Primarily, I write this email because I haven't seem this potential of
reflection be mentioned yet in my reading or listening to CPPcon talks.
Libraries being able to easily write static checks to make sure you are
using them correctly seems very useful, and it is in the two intersection
of improving safety and reflection, both of which lots of design work is
being done on currently. So keeping this possibility in mind when doing
such design work might be useful.
Thank you for reading my thoughts..
Emily Ammundsen
P.S. if we can reflect over namespaces, then adding a single
`static_assert` at the end of a compilation unit could do linting for
naming conventions by recusing over all the visible names. If there was a
`static_assert_delay` which ran a static_assert as if it was at the end of
the compilation unit, then a header included at the beginning could
enforce naming conventions. With sufficiently powerful reflection
abilities, we don't need the triggers since we can just iterate over all
the code and catch the accesses we care about.
Received on 2025-09-21 22:11:13