Live data from Hacker News

Reflection for C++26

isocpp.org

21–30 of 214 posts

Re: Reflection for C++26

#21
post #11
post #7

Finally. I think there have been proposals since C++17 at least, and all I really wanted is for them to solve the common problem of basic static reflection for enums (without hacks like magic_enum uses).

magic_enum is killing my build time with endless template instantiations. Is this going to be faster?

magic_enum works by walking all possible enumeration values from one-by-one in a wide range at compile time, instantiating a function template for each one so it can extract the __PRETTY_FUNCTION__ name, which is very slow. The C++26 feature just directly returns the vector of the named enumerators in one go, so it should be way faster.

They have a reference implementation on godbolt under clang, so you can play around with that. I did not try it yet.

Re: Reflection for C++26

#22
Wow this got really long. I was one of the coauthors for a reflection proposal (N3340) over a dozen years ago. Implementing compile-time reflection is honestly trivial - you basically transfer data from the symbol table on-demand into template specializations. It was roughly 1500 LOC to modify g++ to do it.

Looking at the examples (https://isocpp.org/files/papers/P2996R4.html#examples) what really stands out is the direct integration of type-syntax into the language. It fits in with a certain token-substitution way that connects back to templates. It also replaces some of the uglier operators (typeof?).

I hope it goes int! During the language's stagnation I left for a while, perhaps it'll be competitive again soon.

Re: Reflection for C++26

#23

Can I ask a naive question that consists of two parts and please don't flame me? lol * What type of problems static reflection could solve, in general? * Are there specific cases and / or situations where static reflection could resolve such case, even simplify an unnecessary complexity?

> What type of problems static reflection could solve, in general? Imagine making a plain struct Point { float x; float y; }; and wanting to serialize it to JSON without further ceremony

This is the thing that's driving me away from C++ very quickly. A big part of our code base is code that handles this, and it either has to be in a DSL and constantly recompiled or we have to make a bunch of boilerplate. It's a huge problem for the language not to be able to do this.

Re: Reflection for C++26

#24

Can I ask a naive question that consists of two parts and please don't flame me? lol * What type of problems static reflection could solve, in general? * Are there specific cases and / or situations where static reflection could resolve such case, even simplify an unnecessary complexity?

Here are some examples from the linked paper

* Converting enum values to strings, and vice versa

* Parsing command line arguments from a struct definition (like Rust's clap)

* Simple definition of tuple and variant types, without the complex metaprogramming tricks currently used

* Automatic conversion between struct-of-arrays and array-of-structs form

* A "universal formatter" that can print any struct with all its fields

* Hashing a struct by iterating over its fields

* Convert between a struct and tuple, tuple concatenation, named tuples

Re: Reflection for C++26

#25
I have been waiting for static reflection for the last 20 years. The current proposal seems quite nice, but the real question is whether any non trivial usage will kill compilation performance.

Re: Reflection for C++26

#26

Can I ask a naive question that consists of two parts and please don't flame me? lol * What type of problems static reflection could solve, in general? * Are there specific cases and / or situations where static reflection could resolve such case, even simplify an unnecessary complexity?

Any sort of reflection brings C++ one step closer to Python.

Implementing serialization for complex types often requires manual code writing or external tools. With static reflection you could automate this process

  template
  void serialize(const T& obj, std::ostream& os) {
      for_each(reflect(T), [&](auto member) {
          os 
Simplified property systems

    class Person {
    public:
        Person(const std::string& name, int age)
            : name(name), age(age) {}

        std::string getName() const { return name; }
        void setName(const std::string& name) { this->name = name; }

        int getAge() const { return age; }
        void setAge(int age) { this->age = age; }

    private:
        std::string name;
        int age;

        REFLECT_PROPERTIES(
            (name, "Name of the person"),
            (age, "Age of the person")
        )
    };

    int main() {
        Person person("Alice", 30);

        auto properties = reflect::getProperties();

        for (const auto& prop : properties) {
            std::cout 
Simplified template metaprogramming

    template
    void printTypeInfo() {
        constexpr auto info = reflect(T);
        std::cout 
Easier to write generic algorithms that work with arbitrary types

    template
    void printAllMembers(const T& obj) {
        for_each(reflect(T), [&](auto member) {
            std::cout 

Re: Reflection for C++26

#27
post #10
post #8

Earlier quoted context omitted.

You don't need RTTI to deserialize data in a clean way. What you need is return-type polymorphism. Haskell has this and it makes writing serializers and deserializers symmetric and totally painless.

Return type polymorphism and inheritance doesn't mix very well. Swift got into this mess early in it's lifecycle and it's type checking is still more expensive than the rest of the compiler combined, and unpredictable on top of that.

Yeah if you ask me, inheritance is the one to go. Every time. Inheritance just makes things more complicated. It’s not a great tool of abstraction.

Re: Reflection for C++26

#28

Wow this got really long. I was one of the coauthors for a reflection proposal (N3340) over a dozen years ago. Implementing compile-time reflection is honestly trivial - you basically transfer data from the symbol table on-demand into template specializations. It was roughly 1500 LOC to modify g++ to do it. Looking at the examples ( https://isocpp.org/files/papers/P2996R4.html#examples ) what really stands out is the…

By ”stagnation” do you mean “not getting new features”?

Re: Reflection for C++26

#29

Can I ask a naive question that consists of two parts and please don't flame me? lol * What type of problems static reflection could solve, in general? * Are there specific cases and / or situations where static reflection could resolve such case, even simplify an unnecessary complexity?

Here are some examples from the linked paper * Converting enum values to strings, and vice versa * Parsing command line arguments from a struct definition (like Rust's clap) * Simple definition of tuple and variant types, without the complex metaprogramming tricks currently used * Automatic conversion between struct-of-arrays and array-of-structs form * A "universal formatter" that can print any struct with all its f…

Converting enum values to strings, and vice versa

    enum class Color { Red, Green, Blue };

    template
    std::string enum_to_string(E value) {
        constexpr auto enum_info = reflect(E);
        for (const auto& enumerator : enum_info.enumerators()) {
            if (enumerator.value() == value) {
                return std::string(enumerator.name());
            }
        }
        return "Unknown";
    }

    template
    E string_to_enum(const std::string& str) {
        constexpr auto enum_info = reflect(E);
        for (const auto& enumerator : enum_info.enumerators()) {
            if (enumerator.name() == str) {
                return enumerator.value();
            }
        }
        throw std::invalid_argument("Invalid enum string");
    }

Parsing command line arguments from a struct definition

    struct CLIOptions {
        std::string input_file;
        int num_threads = 1;
        bool verbose = false;
    };

    template
    T parse_cli_args(int argc, char* argv[]) {
        T options;
        constexpr auto struct_info = reflect(T);

        for (int i = 1; i 
Simple definition of tuple and variant types

    // Common data structure used in examples below

    struct Person {
        std::string name;
        int age;
        double height;
    };

    // Tuple, without reflection

    int main() {
        std::tuple person_tuple{"John Doe", 30, 175.5};

        std::cout (person_tuple) (person_tuple) (person_tuple)  person_tuple{"John Doe", 30, 175.5};

        std::apply([](const auto&... args) {
            (..., (std::cout  var;

        var = 42;
        std::cout (var) (var) (var);
        std::cout ;
            if constexpr (std::is_same_v)
                std::cout )
                std::cout )
                std::cout  var;

        var = 42;
        std::cout (var) (var) );
            std::cout 
Automatic conversion between struct-of-arrays and array-of-structs

    template
    auto soa_to_aos(const StructOfArrays& soa) {
        std::array aos;
        constexpr auto struct_info = reflect(Struct);

        for (size_t i = 0; i 
    auto aos_to_soa(const std::array& aos) {
        StructOfArrays soa;
        constexpr auto struct_info = reflect(Struct);

        for (size_t i = 0; i 
Universal formatter:

    template
    std::string format(const T& obj) {
        std::ostringstream oss;
        constexpr auto type_info = reflect(T);

        oss 
Hashing a struct by iterating over its fields:

    template
    size_t hash_struct(const T& obj) {
        size_t hash = 0;
        constexpr auto type_info = reflect(T);

        for (const auto& member : type_info.members()) {
            hash ^= std::hash{}(member.get(obj)) + 0x9e3779b9 + (hash > 2);
        }
        return hash;
    }

Convert between struct and tuple, tuple concatenation, named tuples:

    // Struct to tuple
    template
    auto struct_to_tuple(const Struct& s) {
        return std::apply([&](auto&&... members) {
            return std::make_tuple(members.get(s)...);
        }, reflect(Struct).members());
    }

    // Tuple to struct
    template
    Struct tuple_to_struct(const Tuple& t) {
        Struct s;
        std::apply([&](auto&&... members) {
            ((members.set(s, std::get(t))), ...);
        }, reflect(Struct).members());
        return s;
    }

    // Tuple concatenation
    template
    auto tuple_concat(Tuples&&... tuples) {
        return std::tuple_cat(std::forward(tuples)...);
    }

    // Named tuple
    template
    struct NamedTuple {
        REFLECT_NAMED_MEMBERS(Members...);
    };

Re: Reflection for C++26

#30

Earlier quoted context omitted.

> What type of problems static reflection could solve, in general? Imagine making a plain struct Point { float x; float y; }; and wanting to serialize it to JSON without further ceremony

This is the thing that's driving me away from C++ very quickly. A big part of our code base is code that handles this, and it either has to be in a DSL and constantly recompiled or we have to make a bunch of boilerplate. It's a huge problem for the language not to be able to do this.

Example of serializing a C++ object to JSON with reflection:

    template
    std::string to_json(const T& obj) {
        std::ostringstream oss;
        constexpr auto type_info = reflect(T);

        if constexpr (type_info.is_fundamental()) {
            // Fundamental types (int, float, etc.)
            if constexpr (std::is_same_v) {
                oss ) {
                oss ) {
                oss >) {
            // Arrays and vectors
            oss >) {
            // Maps
            oss  hobbies;
        std::map scores;
    };

    int main() {
        Person person {
            "John Doe",
            30,
            175.5,
            Color::Blue,
            {"123 Main St", "Anytown", 12345},
            {"reading", "hiking", "coding"},
            {{"math", 95}, {"history", 88}, {"science", 92}}
        };

        std::cout 
Post reply on HN