←back to thread

196 points svlasov | 1 comments | | HN request time: 0.208s | source
Show context
stefanos82 ◴[] No.40851614[source]
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?
replies(8): >>40851661 #>>40851675 #>>40851709 #>>40851795 #>>40851942 #>>40852552 #>>40853159 #>>40853615 #
edflsafoiewq ◴[] No.40851795[source]
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

replies(1): >>40852011 #
greenavocado ◴[] No.40852011[source]
Converting enum values to strings, and vice versa

    enum class Color { Red, Green, Blue };

    template<typename E>
    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<typename E>
    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<typename T>
    T parse_cli_args(int argc, char* argv[]) {
        T options;
        constexpr auto struct_info = reflect(T);

        for (int i = 1; i < argc; i++) {
            std::string arg = argv[i];
            for (const auto& member : struct_info.members()) {
                if (arg == "--" + std::string(member.name())) {
                    if (member.type() == typeid(bool)) {
                        member.set(options, true);
                    } else if (i + 1 < argc) {
                        member.set(options, std::string(argv[++i]));
                    }
                    break;
                }
            }
        }
        return options;
    }

    
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<std::string, int, double> person_tuple{"John Doe", 30, 175.5};

        std::cout << "Name: " << std::get<0>(person_tuple) << std::endl;
        std::cout << "Age: " << std::get<1>(person_tuple) << std::endl;
        std::cout << "Height: " << std::get<2>(person_tuple) << std::endl;

        Person p{"Jane Doe", 25, 165.0};
        auto p_tuple = std::make_tuple(p.name, p.age, p.height);

        return 0;
    }

    // Tuple, with reflection

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

        std::apply([](const auto&... args) {
            (..., (std::cout << reflect(args).name() << ": " << args << std::endl));
        }, person_tuple);

        Person p{"Jane Doe", 25, 165.0};
        auto p_tuple = std::apply([&p](auto... members) {
            return std::make_tuple(members.get(p)...);
        }, reflect(Person).members());

        return 0;
    }

    // Variant, without reflection

    int main() {
        std::variant<int, std::string, Person> var;

        var = 42;
        std::cout << "Variant holds: " << std::get<int>(var) << std::endl;

        var = "Hello, World!";
        std::cout << "Variant holds: " << std::get<std::string>(var) << std::endl;

        var = Person{"Alice", 28, 170.0};
        const auto& p = std::get<Person>(var);
        std::cout << "Variant holds Person: " << p.name << ", " << p.age << ", " << p.height << std::endl;

        std::visit([](const auto& v) {
            using T = std::decay_t<decltype(v)>;
            if constexpr (std::is_same_v<T, int>)
                std::cout << "Int: " << v << std::endl;
            else if constexpr (std::is_same_v<T, std::string>)
                std::cout << "String: " << v << std::endl;
            else if constexpr (std::is_same_v<T, Person>)
                std::cout << "Person: " << v.name << std::endl;
        }, var);

        return 0;
    }

    // Variant, with reflection

    int main() {
        std::variant<int, std::string, Person> var;

        var = 42;
        std::cout << "Variant holds: " << std::get<int>(var) << std::endl;

        var = "Hello, World!";
        std::cout << "Variant holds: " << std::get<std::string>(var) << std::endl;

        var = Person{"Alice", 28, 170.0};
        
        std::visit([](const auto& v) {
            constexpr auto type_info = reflect(std::decay_t<decltype(v)>);
            std::cout << "Variant holds " << type_info.name() << ": ";
            if constexpr (type_info.is_class()) {
                for (const auto& member : type_info.members()) {
                    std::cout << member.name() << ": " << member.get(v) << ", ";
                }
            } else {
                std::cout << v;
            }
            std::cout << std::endl;
        }, var);

        return 0;
    }

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

    template<typename Struct, size_t N>
    auto soa_to_aos(const StructOfArrays<Struct, N>& soa) {
        std::array<Struct, N> aos;
        constexpr auto struct_info = reflect(Struct);

        for (size_t i = 0; i < N; ++i) {
            for (const auto& member : struct_info.members()) {
                member.set(aos[i], soa.get(member.name())[i]);
            }
        }
        return aos;
    }

    template<typename Struct, size_t N>
    auto aos_to_soa(const std::array<Struct, N>& aos) {
        StructOfArrays<Struct, N> soa;
        constexpr auto struct_info = reflect(Struct);

        for (size_t i = 0; i < N; ++i) {
            for (const auto& member : struct_info.members()) {
                soa.get(member.name())[i] = member.get(aos[i]);
            }
        }
        return soa;
    }

Universal formatter:

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

        oss << type_info.name() << " {\n";
        for (const auto& member : type_info.members()) {
            oss << "  " << member.name() << ": " << member.get(obj) << ",\n";
        }
        oss << "}";
        return oss.str();
    }
Hashing a struct by iterating over its fields:

    template<typename T>
    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<decltype(member.get(obj))>{}(member.get(obj)) + 0x9e3779b9 + (hash << 6) + (hash >> 2);
        }
        return hash;
    }

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

    // Struct to tuple
    template<typename Struct>
    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<typename Struct, typename Tuple>
    Struct tuple_to_struct(const Tuple& t) {
        Struct s;
        std::apply([&](auto&&... members) {
            ((members.set(s, std::get<members.index()>(t))), ...);
        }, reflect(Struct).members());
        return s;
    }

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

    // Named tuple
    template<typename... Members>
    struct NamedTuple {
        REFLECT_NAMED_MEMBERS(Members...);
    };
replies(1): >>40852303 #
fluoridation ◴[] No.40852303[source]
This is terrible. You can't just do Enum::Member.str or something?
replies(5): >>40852845 #>>40853083 #>>40853438 #>>40854151 #>>40855154 #
1. andersa ◴[] No.40853438[source]
Of course not. We must involve 10 layers of templates that mere mortals cannot read and compilers cannot process in reasonable time so the academics at the committee will be happy.

Addressing real problems with simple solutions isn't allowed.