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 names

In 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.