C++ Logo

sg7

Advanced search

Request for supporting and enumerating user-defined attributes

From: Jeremy Ong <jeremycong_at_[hidden]>
Date: Sun, 15 Oct 2023 20:59:43 -0600
First, a hearty thanks to the authors of P2996R0 (
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2996r0.html).

One item that appeared to be absent from the discussion is the notion of
user-defined attributes. My feedback largely stems from the games industry,
where virtually every game and game engine employs reflection to some
degree, but I suspect that these use cases generalize well. To be clear,
the intent isn't to wave my hands and create work, but hopefully to offer a
different perspective regarding how custom C++ serialization is used in the
games industry today.

Given a data struct, this type of pattern will be seen in many game engines
(fairly contrived pseudocode):

```
class PlayerState
{
public:
    // This is a member function that is exposed to gameplay scripts
automatically (e.g. Lua)
    [[scriptable]]
    void teleport(float3 position);

    // This is a property that is serialized when the player is serialized.
    // In addition, when the property changes, the changes are replicated
across the network
    // for multiplayer games.
    [[serializable]]
    [[client_server]]
    int health;

    // ...
};
```

The functionality above is generally provided by some sort of compiler
extension or (more commonly) a custom parser/preprocessing step. The idea
is to embed additional metadata about member variables, functions, and
types that are used across the build and tooling ecosystem. Other examples
of such attributes that I've seen:

- A "client only" or "server only" attribute used to control how and where
data is replicated for multiplayer games
- An "editor function" attribute used to indicate that a function should be
bound to the engine's scripting context
- A "gpu constant" attribute used to inform the build system to validate
that the member alignments match the layout expected by the GPU
- A "description" attribute used to populate tooltips and help text for a
given enum field, member variable, or function.
A "quantization" attribute used to indicate that on serialization, the
value may be compressed/packed.

These are just a few examples among hundreds of attributes that I have
encountered across multiple large-scale game engines at multiple companies.
Notice also that some attributes given in the last couple of examples are
parameterized to accept a value as well (e.g. [[description = "some helpful
text"]]).

To give more concrete examples, here are example attributes supported by
Unreal Engine (which they refer to as "specifiers"):

- Class attributes:
https://docs.unrealengine.com/4.27/en-US/ProgrammingAndScripting/GameplayArchitecture/Classes/Specifiers/
- Function attributes:
https://docs.unrealengine.com/4.27/en-US/ProgrammingAndScripting/GameplayArchitecture/Functions/Specifiers/
- Property attributes:
https://docs.unrealengine.com/4.27/en-US/ProgrammingAndScripting/GameplayArchitecture/Properties/Specifiers/
- Struct attributes:
https://docs.unrealengine.com/4.27/en-US/ProgrammingAndScripting/GameplayArchitecture/Structs/Specifiers/

Such systems are sufficiently mature that I don't think anyone could
reasonably expect the first real incarnation of C++ reflection to cover all
existing use-cases, but perhaps such examples in the wild may be useful in
motivating additional feature coverage. I think even just non-parameterized
attributes supported by the splice and meta concepts introduced in the
reflection proposal would get significant mileage and go a long way towards
eventually deprecating existing custom pre-compiler frontends.

Thanks for reading,
Jeremy

Received on 2023-10-16 02:59:56