Dobiasd / FunctionalPlus

Functional Programming Library for C++. Write concise and readable C++ code.
http://www.editgym.com/fplus-api-search/
Boost Software License 1.0
2.1k stars 167 forks source link

get for `variant` #285

Closed tom91136 closed 9 months ago

tom91136 commented 9 months ago

I might be missing something but I can't seem to find a built-in way to extract a value from a variant. Adding it outside of variant is quite unsatisfactory as well because we need to use the visitor just to do a transform. Would something like the following be appropriate?

    template <typename T>
    maybe<T> get() const {
        static_assert(
            internal::is_one_of<T, Types...>::value
            , "Type must match one possible variant type.");

        if (auto ptr = std::get<internal::get_index<T, Types...>::value>(shared_ptrs_); ptr) {
            return just<T>(*ptr);
        } else {
            return nothing<T>();
        }
    }

Partially related: I think a non-returning visit would be useful as well, the current visit would fail if any the cases return void, I was able to work around this with:

        if constexpr (std::is_same_v<Res, void>) {
            ([&]() -> bool {
                if (auto ptr =
                        std::get<internal::get_index<
                                typename internal::function_first_input_type<Fs>::type, Types
                                ...>::value
                        >(shared_ptrs_); ptr) {
                    fs(*ptr);
                    return true;
                }
                return false;
            }() || ...);
            return;
        } else {
            const auto results = justs(visit_helper<Res>(fs...));
            assert(size_of_cont(results) == 1);
            return head(results);
        }

Are PRs welcome?

Dobiasd commented 9 months ago

Hi, and thanks for the good suggestion. :+1:

You're right. There currently is no built-in way to just get the content of a variant. One would need to use a visitor like this:

const fplus::variant<int, std::string> x(3);
const fplus::maybe<int> y = x.visit_one(fplus::identity<int>);

And, yeah, using a .get member function, like you suggested, looks better:

const fplus::variant<int, std::string> x(3);
const fplus::maybe<int> y = x.get<int>();

I could be implemented like this:

    template <typename T>
    maybe<T> get() const
    {
        return visit_one(identity<T>);
    }

I think we should add something like this to variant.cpp in FunctionalPlus (and have a test for it in variant_test.cpp).

Dobiasd commented 9 months ago

Regarding the non-returning visit, I guess if one uses a visitor function that returns nothing (void), one is executing a side effect. So maybe it makes sense to separate it from the value-retutning visitor pattern, which is usually used without side effects. Then, an implementation of a new return-less visit_one member function could look like this:

    template<typename F>
    void effect(F f) const
    {
        using T = typename internal::function_first_input_type<F>::type;
        const auto ptr =
            std::get<internal::get_index<T, Types...>::value>(shared_ptrs_);
        if (ptr)
        {
            return internal::invoke(f, *ptr);
        }
    }

Usage example:

void print_to_cout(int x)
{
    std::cout << x << std::endl;
}

const fplus::variant<int, std::string> x(3);
x.effect(print_to_cout);

What do you think? :slightly_smiling_face:

Dobiasd commented 9 months ago

And yes, PRs are generally welcome; ideally with a small added unit test (for this case in variant_test.cpp. The CI will take care of making sure that the change works with C++14. :office_worker:

tom91136 commented 9 months ago

Thanks, let me put something together. I didn't know it needs to be C++14 so I guess the fold-expression and constexpr-if needs to go 😢

Dobiasd commented 9 months ago

Yeah, I mean we could raise the compiler requirement for FunctionalPlus from C++14 to C++17, but I'd prefer to only do this if really needed.