Vitaly Protasov
Vitaly Protasov

Reputation: 179

ranges::view pipe operator - monadic chains for std::visit by hide std::variant params to variants->options pattern?

I'm inspired with std::ranges and std::view and their | operator that help to chain different algorithms, so I wish to pack there std::get_if and std::visit patters inside under the hood to do inplace constructable non modifying (constexpr?) wrapper that got my std::variant<...> and I can specify for which types I do job in lambda idea is use it as selftink pattern "Optional Variant Visitor View?" a chained monadic view approach with injector << for nullopt value C++17 - C++20 and idea to use template concepts instead of direct c++14 style concepts.

example of usage:

int main() {
   std::optional<std::variant<int, float, bool>> myVariant = 42;
   /* NOTE: u can use any order when building visiting chain
   but don't repeat sequentially or non sequential same types like passing type<int> .... 
   others type<int> since second should generate static assert "int handler proceed 
   before" on first duplication occured */
   variants(myVariant) << no_option([]() { /* what we do when no variant i.e std::nullopt - comment if unneeded*/; })
        | on_type<bool>([](const auto& value) {})
        | on_type<int>([](const auto& value) {}) 
    //  | on_type<string>([](const auto& value) {}) // should produce static assert type handler for type not listed in variant
        | on_type<float>([](const auto& value) {})
    //  | on_type<int>([](const auto& value) {}) // should produce static_assert duplicate in handler chain
        | no_type(); // chain close checker will produce static assert that we forget to add some type(s) if so, that listed in variant, comment if not all types needed and we forced skip handling there

   return 0;
}

Here is code that I can't get done to work with type checking in compile time to produce proper static assertion and don't have clue to finish my unordered_set to keep user from duplication for handlers, like apply SFINAE - requires etc...

template <typename T>
struct on_type {
    using type_t = T;
    on_type(std::function<void(const T&)> callback) : callback_func(callback) {}

    void if_type(const T& value) const { callback_func(value); }
    std::function<void(const T&)> callback_func;
};

struct no_type {
    // TODO: add decltype decay etc to print type in static assert
    template <bool asserted, typename unhandled_in_chain>
    void if_true() const { static_assert(asserted, "type X not handled in chain"); }
};

struct no_option {
    no_option(std::function<void()> callback) : callback_func(std::move(callback)) {}
    void if_true() const { callback_func(); }
    std::function<void()> callback_func;
};

template <typename... Ts>
struct VariantHandler {

    using variant_t = std::variant<Ts...>;
    using optional_variant_t = std::optional < variant_t >;

    VariantHandler( const variant_t& from ) : variant_holder(from) {}
    VariantHandler( const optional_variant_t &from ) {
        if (from.has_value())
            variant_holder = from;
        else
            variant_holder = std::nullopt;
    }

    optional_variant_t variant_holder;

    mutable std::unordered_set<std::size_t> processedTypes{};

    template <typename T>
    const VariantHandler< Ts... > & operator | (const on_type<T>& type_handler) const {
        // FIXME expression don't evaluate to constant
        //static_assert(processedTypes.contains(typeid(T).hash_code()) == 0, "Duplicate type handler encountered");
        processedTypes.insert(typeid(T).hash_code());

        if (true == variant_holder.has_value()) {
            std::visit([&](const auto& value) {
                if constexpr (std::is_same_v<std::decay_t<decltype(value)>, T>) {
                    type_handler.if_type(value);
                }
            }, variant_holder.value());
        }

        return *this; // Or return a proxy object for further chaining
    }

    const VariantHandler< Ts... > operator << (const no_option &on_true) const {
        if (false == variant_holder.has_value())
            on_true.if_true();
        return *this;
    }

    // called for all remaining types in variant that not handled through on_type chain
    //template <typename T>
    constexpr void operator | (const no_type& when_no_type) const {
        // TODO: how we check that type handler not registered when listed through | operator in chain?
        when_no_type.if_true<true, bool>();
    }

    // TODO: determine which type we forget to add listener if we use no_type static last param it will assert to us
};

// TODO: add 'requires' to valid variant types check and static assert for on_type<T> where T is not part of Ts...
template <typename... Ts>
constexpr auto variants(const std::variant<Ts...>& variant) {
    return VariantHandler<Ts...>{variant};
}
template <typename... Ts>
constexpr auto variants(const std::optional<std::variant<Ts...>>& optional_variant) {
    return VariantHandler<Ts...>{optional_variant};
}

Upvotes: 1

Views: 141

Answers (1)

Vitaly Protasov
Vitaly Protasov

Reputation: 179

So I made works range view based approach to build visiting the optional variants utility class, thanks for all recommendations! https://github.com/ivitaly/variant_view/tree/main

Upvotes: 0

Related Questions