CallMeMaybe: runtime reflection for C++26 without RTTI
Runtime reflection library built on C++26 static reflection
At a glance
- What is it?
- CallMeMaybe is a header-only C++ library that builds a runtime registry on top of P2996 static reflection, letting you look up types by string and invoke annotated members dynamically. It needs GCC 16.1 with -freflection, and it does not manage the lifetime of what it constructs.
- Who is it for?
- Adopt CallMeMaybe if you are already compiling with GCC 16.1 and -freflection and you want string-keyed construction and invocation of annotated class members without pulling in RTTI. Do not adopt it for standard library containers, overloaded free functions, or anything where you cannot accept manual delete of objects the library constructs.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 107 days ago.
- What is it written in?
- Mainly C++, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 30, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The gap CallMeMaybe fills between compile-time reflection and runtime dispatch
P2996 gives C++ a way to inspect the program's own structure at compile time. What it does not give you is a name-to-entity map that survives into the running binary, which is what you need for plugin loading, scripting bindings, serialization driven by field names, or an editor that edits objects it was not compiled against. CallMeMaybe builds that map. The README describes it as a runtime reflection library built on top of P2996 static reflection, and says it purposefully mirrors many of the std::meta functions so the interface feels familiar to anyone who has read the P2996 paper. The audience is narrow and specific: C++ engineers on the experimental edge of the language who need dynamic invocation and instantiation, and who are willing to compile with a pre-release toolchain to get it. The project also implements a custom type system, which the README says lets it completely avoid RTTI requirements. That matters for builds where RTTI is disabled or where typeid is unavailable, and it is the main architectural decision in the library.
How the annotation-driven registry actually works
The mechanism has three stages, and the README lays them out explicitly. First you tag the methods, members, or constructors you want exposed with [[=cmm::reflectable]]. Unannotated members are ignored, which is the opt-in boundary: the registry only knows what you told it about. Second, at startup, you register the type into a global runtime registry with cmm::register_rrefl<^^Type>(). The ^^ syntax is P2996's reflection operator, and it is what feeds the static reflection into the runtime structure. Third, you look entities up by string and invoke them. The lookup side uses cmm::reflect_name("Player") to get a cmm::info handle for a type, cmm::lookup::get_constructor<std::string, int>(p_id) to find a constructor by its parameter types, and cmm::lookup::get_member(p_id, "greet") to find a method by name. Invocation goes through cmm::invoke, which is templated on the return type when you want the result back. The library also exposes std::meta-shaped traversal: the second README example iterates cmm::nonstatic_data_members_of(p_id) and, for each member, reads cmm::identifier_of, cmm::offset_of, and cmm::type_of. That is a real data flow: annotation, then registration, then string lookup, then a handle, then a call. Nothing here is magic at runtime; the registry is populated by code the compiler generated from your annotations.
Installing CallMeMaybe and running the first dynamic call
There is no package manager step. The README says the library is available in the include/cmm/ directory and that you should copy include/cmm into your project and include "cmm/meta.hpp". So the install is a directory copy, and the repository layout matches: the top level holds CMakeLists.txt, LICENSE, README.md, examples/, and include/. The example program lives at examples/basic_usage.cpp. To build it, the README gives this sequence, with the compiler set through an environment variable:
CXX=g++-16 cmake -B build
cmake --build build
./build/bin/basic_usageThe CXX=g++-16 prefix is not decoration. The README's usage notes state that the library currently requires GCC 16.1 with the -freflection flag, so a default compiler will not do. Once it builds, the smallest useful program registers one type and calls one method by name. The README's first example defines a Player class with an annotated constructor and an annotated greet method, then does this in main:
cmm::register_rrefl<^^Player>();
cmm::info p_id = cmm::reflect_name("Player");
cmm::info ctor_id = cmm::lookup::get_constructor<std::string, int>(p_id);
cmm::Value player_val = cmm::invoke(ctor_id, std::string("Alice"), 25);
Player* p_ptr = player_val.get<Player*>();
cmm::info greet_id = cmm::lookup::get_member(p_id, "greet");
std::string response = cmm::invoke<std::string>(greet_id, p_ptr, std::string("Bob"));What you should see is the string "Alice says: hello, Bob!" printed, because the constructor ran with the arguments you passed by string lookup and the method ran against the resulting pointer. The delete p_ptr line at the end of the example is the part people skip when reading, and it is load-bearing.
The limitations the README admits, and the ones it implies
The usage notes are unusually candid. Standard library containers and complex generic types are not natively supported by the reflection registry yet, which rules out the most common serialization use case: reflecting over a struct that holds a std::vector or a std::map. Free function overloads sharing the same identifier are not automatically disambiguated, so a lookup by name alone will not tell you which overload you get; constructors are looked up by parameter types, but free functions have no equivalent shown. Callers are responsible for manually deleting instances created via dynamic constructor invocations. That is a raw ownership handoff: cmm::invoke returns a cmm::Value from which you extract a Player*, and nothing in the library will free it for you. In an exception path, that is a leak waiting to happen unless you wrap it. There is also the toolchain constraint, which is the largest one. The README states GCC 16.1 with -freflection is required and warns that the experimental Clang version may lead to hash collisions right now. A hash collision in a name-keyed registry is not a cosmetic bug; it means a lookup can return the wrong entity. If you are evaluating this for production, that sentence should end the evaluation for now.
CallMeMaybe against the reflection routes you already have
The obvious alternative is Qt's meta-object system. It also gives you string-keyed method invocation and property iteration, and it has done so for years with a stable compiler baseline. The difference in approach is where the metadata comes from. Qt requires a moc preprocessor pass and a QObject inheritance chain, so your reflected types must derive from QObject and be processed by an external tool. CallMeMaybe derives its metadata from P2996 static reflection at compile time, needs no separate code generator, and imposes no base class. The cost is the opposite: Qt works on today's compilers, CallMeMaybe needs GCC 16.1 with -freflection. A second alternative is hand-written registration: a map from std::string to std::function plus a factory lambda per type. That is portable, debuggable, and works everywhere. It also means every new reflected member is a manual edit in two places, which is exactly the duplication the annotation approach removes. If your reflected surface is ten members, write the map. If it is hundreds and changing weekly, the annotation model earns its toolchain cost. A third route, RTTI plus typeid and dynamic_cast, solves a different problem: it identifies types at runtime but does not give you names, members, or offsets, and CallMeMaybe's custom type system exists specifically to avoid depending on it.
Maintenance, licence, and what upgrading costs you
The last push to the repository was on 2026-06-16. There are no retrieved releases, so there is no versioned artifact to pin and no changelog to read before upgrading; you track the main branch or you copy include/cmm and freeze it yourself. Since the install model is a directory copy rather than a package, the upgrade model is the same: diff the include/cmm tree against the copy in your project and take the changes you want. That is a real advantage for reproducibility and a real cost for security fixes, because nothing will notify you. The licence is Apache-2.0, per the repository's LICENSE file and the badge at the top of the README. Apache-2.0 is permissive and includes an explicit patent grant, which is friendlier than MIT for a library whose value is in a compiler-adjacent technique. If you redistribute CallMeMaybe inside a product, the usual Apache-2.0 obligations apply: keep the licence and notice files, and state significant changes. This is not legal advice; check with counsel if the patent grant or the notice requirements matter to your distribution model. The other ongoing cost is toolchain churn. A library built on a paper that is still being revised will track that paper, and the -freflection flag's behaviour is not frozen. Budget for the possibility that a GCC update changes how your annotations compile.
Editorial conclusion
Adopt CallMeMaybe if you are already compiling with GCC 16.1 and -freflection and you want string-keyed construction and invocation of annotated class members without pulling in RTTI. Do not adopt it for standard library containers, overloaded free functions, or anything where you cannot accept manual delete of objects the library constructs. Before committing, verify that your GCC build accepts -freflection, that the [[=cmm::reflectable]] annotation survives your compiler's parsing, and that you are willing to own deletion of every instance returned by cmm::invoke on a constructor.
Frequently asked questions
What is CallMeMaybe?
It is a C++ runtime reflection library built on top of P2996 static reflection introduced in C++26. It builds a runtime registry so class members can be looked up by string, dynamically instantiated, and invoked, and it uses a custom type system to avoid RTTI requirements.
How do I install CallMeMaybe?
There is no package to install. The README says to copy include/cmm into your project and include "cmm/meta.hpp", then build with GCC 16.1 and the -freflection flag, for example with CXX=g++-16 cmake -B build followed by cmake --build build.
Which compiler does CallMeMaybe require?
The usage notes state that it currently requires GCC 16.1 with the -freflection flag, and that the experimental Clang version may lead to hash collisions right now.
Does CallMeMaybe support standard library containers?
No. The README's usage notes say standard library containers and complex generic types are not natively supported by the reflection registry yet.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/lauriewired-callmemaybe)