wise_enum icon indicating copy to clipboard operation
wise_enum copied to clipboard

Wise enum extension

Open koczwak opened this issue 6 years ago • 4 comments
trafficstars

For our internal use, we extended wise-enum, adding:

  • from_number function (creation of enum similiar to from_string)
  • ostream operator WISE_ENUM(Foo, (Bar, 1)) Foo::Bar -> "Bar(1)"
  • abort call in case of calling from_number/from_string with invalid argument

Do you want any of those features pulled into your project? We can hide them behind defines.

koczwak avatar Apr 04 '19 12:04 koczwak

from_number meaning, you pass the underlying value of the enum?

For ostream, are you saying that operator<< does something to to_string, i.e. "Bar(1)" instead of "Bar"? That seems a bit surprising that ostream wouldn't be consistent with to_string don't you think?

In both cases, why an abort call? Seems like returning an empty optional is much better; it allows for proper error handling and the user is free to call abort if the optional is empty.

quicknir avatar Apr 04 '19 15:04 quicknir

  1. Yes, exactly. It's returning optional value.
  2. We use << for debugging puposes only, and to_string to get textual representation
  3. My appologies, I meant abort in case of to_string function with invalid argument (that is not present in enum, possibly generated by bitflag enum).

koczwak avatar Apr 04 '19 16:04 koczwak

I did a similar thing. very useful.

I decided to make istream operator >> permissive in that it understands text or numbers, on the basis that with IO it's helpful to be strict when writing and permissive when reading.

namespace quoine::wrap::wise_enum
{
    using namespace ::wise_enum;

    template < class T, std::enable_if_t< wise_enum::is_wise_enum_v< T > > * = nullptr >
    void wise_enum_from_stream(std::istream &is, T &arg)
    {
        auto buffer = std::string();
        is >> buffer;
        if (is)
        {
            auto o = wise_enum::from_string< T >(buffer);
            if (o.has_value())
                arg = *o;
            else
            {
                using int_type = std::underlying_type_t< T >;
                auto num       = static_cast< int_type >(std::atoll(buffer.c_str()));
                bool found     = false;
                for (auto e : wise_enum::range< T >)
                {
                    if (static_cast< int_type >(e.value) == num)
                    {
                        arg   = e.value;
                        found = true;
                        break;
                    }
                }
                if (not found)
                    is.setstate(std::ios::failbit);
            }
        }
    }
}   // namespace quoine::wrap::wise_enum

#define WISE_ENUM_ENABLE_IO(Enum)                                                                                                  \
    inline auto operator<<(std::ostream &os, Enum e)->std::ostream & { return os << ::quoine::wrap::wise_enum::to_string(e); }     \
    inline auto operator>>(std::istream &is, Enum &e)->std::istream &                                                              \
    {                                                                                                                              \
        ::quoine::wrap::wise_enum::wise_enum_from_stream(is, e);                                                                   \
        return is;                                                                                                                 \
    }

#define WISE_ENUM_MEMBER_ENABLE_IO(Enum)                                                                                           \
    friend auto operator<<(std::ostream &os, Enum e)->std::ostream & { return os << ::quoine::wrap::wise_enum::to_string(e); }     \
    friend auto operator>>(std::istream &is, Enum &e)->std::istream &                                                              \
    {                                                                                                                              \
        ::quoine::wrap::wise_enum::wise_enum_from_stream(is, e);                                                                   \
        return is;                                                                                                                 \
    }

madmongo1 avatar May 18 '19 06:05 madmongo1

@karol-koczwara-ig @madmongo1 I kind of want to be careful here because obviously there are infinite things that could be added. My main goal is to keep a relatively small set of basis functions that you should be able to implement other things "on top of", non-intrusively (so you can use wise_enum the way you want without needing to fork it).

Right now, I think I will do from_integer with similar API to from_string. It will be implemented using another public function, verify_enum, which takes an enum and returns a bool, and simply verify that the value passed in is actually a valid value (which users might find useful for other things).

I'm not really sure about the stream operators. Using operator<< as the debug representation doesn't seem like a very standard thing to do. Different people might want different debug representations too (and not everyone even uses/likes streams), so I think maybe it's just better to let users do what they want on top of to_string. Again, as @madmongo1 's code snippet shows, it's not necessary to modify wise_enum itself to get the desired behavior.

I'm not really sure why to_string would abort for an invalid argument. Currently it returns an empty string; some people may want to do something other than abort with this. If you want to abort, then you can wrap that function with my_to_string, do the if check and abort yourself. I don't really want to have lots of #defines that switch behaviors between incompatible states.

quicknir avatar May 18 '19 07:05 quicknir