←back to thread

196 points svlasov | 1 comments | | HN request time: 0.209s | 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 #
j16sdiz ◴[] No.40853083[source]
This is how C++ language usually works. Just some primitive for building libraries on. Expect some library change come later.
replies(1): >>40854204 #
coopierez ◴[] No.40854204[source]
Thankfully it is so easy to quickly import libraries into C++...
replies(2): >>40854324 #>>40859679 #
pjmlp ◴[] No.40854324[source]
It actually is, for anyone using Conan or vcpkg.
replies(1): >>40855473 #
coopierez ◴[] No.40855473[source]
So our team switched to vcpkg recently and, while it has improved certain parts of our dependency process, it has also made other parts more complex. Notably when something suddenly goes wrong it is far more complex to figure out what actually happened (Though to be fair a lot of these issues also come from combining vcpkg with cmake). This led to most of my team revolting against vcpkg and now it looks like we might go back to just vendoring our libraries again.

I suppose I just yearn for an all-in-one build system + package manager like exists in Rust or Go. Once you've seen what can be possible when these things are integrated from the ground up it sort of ruins your C++ build experience!

replies(1): >>40857722 #
1. pjmlp ◴[] No.40857722[source]
Until one needs to step out of a pure Go or pure Rust experience, and then it is a quite interesting build.rs file, Makefiles or shell scripts.