Understanding std::meta::variable_of in C++26 Static Reflection
With the adoption of static reflection (specified in P2996) targeting C++26, C++ developers are getting a compile-time toolkit with unprecedented power. However, diving into the standard reflection API (std::meta) can sometimes lead to head-scratching moments.
One common point of confusion is the function std::meta::variable_of. According to the C++ standard [basic.scope], a function parameter is technically a variable. Why, then, does std::meta::variable_of exist, requiring a function parameter reflection just to return "the reflection of the parameter variable corresponding to r"? Why isn't the parameter already represented as a variable reflection?
Let's unpack the subtle architectural distinction between parameter declarations and variable entities in C++ reflection.
The Paradox: Isn't a Parameter Already a Variable?
In standard C++ terminology, a function parameter declared inside a function definition introduces a variable in the function's outer block scope. So at first glance, having a function like this seems redundant:
consteval info variable_of(info r);If r already reflects a function parameter, and that parameter is a variable, why doesn't parameters_of(^^func) just give us reflections of variables directly?
The Core Reason: Declarations vs. Function Interfaces
The key reason lies in the distinction between an interface declaration and a defined local variable.
1. Function Signatures Don't Always Have Parameter Variables
Consider a typical function prototype or declaration:
void process(int, double); // Parameters have no namesIn this declaration, the function takes an int and a double. There are two parameters in the function's type and interface, but neither parameter introduces a variable you can read from or write to. They don't have storage, names, or variable entities associated with them.
If std::meta::parameters_of(^^process) returned variable reflections, what would it return for unnamed parameters? A variable without a definition or identity is problematic within the meta-object system.
2. Separation of Concerns in Reflection
In the P2996 reflection model, an info value represents a specific semantic entity. A function parameter and a local variable carry completely different metadata:
- Function Parameter Metadata: Does it have a default argument? What is its index in the parameter list? Is it an ellipsis parameter? What function does it belong to?
- Variable Metadata: What is its storage duration? Can its address be taken? What is its value when evaluated?
By keeping the reflection of a parameter distinct from the reflection of the variable introduced by that parameter, the standard avoids conflating signature-level properties with implementation-level properties.
How std::meta::variable_of Works in Practice
When you query the parameters of a function, you receive a range of reflections representing the parameter declarations:
#include <experimental/meta> // Experimental P2996 reflection header
void compute(int x, double y) {
// Inside the function definition, 'x' and 'y' are actual variables
}
consteval void inspect_params() {
constexpr auto params = std::meta::parameters_of(^^compute);
constexpr auto first_param = params[0]; // Reflection of parameter 'int x'
// first_param describes the parameter interface:
// e.g., std::meta::has_default_argument(first_param)
// Obtain the actual variable reflection:
constexpr auto var_x = std::meta::variable_of(first_param);
// var_x describes the variable entity:
// e.g., splicing the variable into an expression
}If you call std::meta::variable_of on a parameter from a function declaration that has no parameter variable (e.g., an unnamed parameter in a forward declaration), the function produces a compile-time error or exception because no corresponding variable entity exists.
Summary
The existence of std::meta::variable_of is a deliberate design choice that prevents API ambiguity:
parameters_of(fn)yields reflections of the parameter declarations in the function's signature.variable_of(param)transitions from the parameter declaration to the variable entity instantiated by that parameter in a function definition.
This design keeps C++26 reflection clean, robust, and capable of handling edge cases like pure declarations, unnamed parameters, and abstract function interfaces.