enum class constructor c++ , how to pass specific value?

后端 未结 2 1923
说谎
说谎 2020-12-06 23:28

I came from Java and here we have such option as set value to constuctor.

Example

enum TYPE
{
    AUTO(\"BMW\"),
    MOTOCYCLE(\"Kawasaki\");

    pr         


        
相关标签:
2条回答
  • 2020-12-06 23:45

    C++ ain't Java! Every language has its own techniques which are a good fit with the language. Don't try to mimic a perfectly fine construct of one language in the same (but broken) construct in a different language.

    Here is how I would solve your issue in C++:

     // Define the actual enumeration
     enum class [[nodiscard]] Vehicle : unsigned char
     {
         CAR,
         MOTORCYCLE,
         SIZE [[maybe_unused]]
     };
    
    // Convert your enumeration to a string (view)
    #include <cassert>
    #include <string_view>
    [[nodiscard]] constexpr auto to_string(Vehicle v) noexcept -> std::string_view {
      assert(v != Vehicle::SIZE);
      switch (v) {
        case Vehicle::CAR:
          return "Car";
        case Vehicle::MOTORCYCLE:
          return "Motorcycle";
      }
    }
    

    To use it, you can do something like:

     for (unsigned char c = 0; c < static_cast<unsigned char>(Vehicle::SIZE); ++c)
            std::cout << to_string(static_cast<Vehicle>(c)) << std::endl;
    

    Writing this every time is a bit cumbersome, however, you could write your own template class that helps with iterating over it. For example:

    #include <type_traits>
    // The generic stuff you only write once
    // Assumes you don't assign any values to your enumeration by hand + it ends on
    // 'SIZE' (unless a second argument was passed if a different name was used)
    template <typename TEnumeration, TEnumeration TSize = TEnumeration::SIZE>
    class [[nodiscard]] EnumRange final {
      using type = std::underlying_type_t<TEnumeration>;
    
     public:
      // The iterator that can be used to loop through all values
      //
      class [[nodiscard]] Iterator final {
        TEnumeration value{static_cast<TEnumeration>(0)};
    
       public:
        constexpr Iterator() noexcept = default;
        constexpr Iterator(TEnumeration e) noexcept : value{e} {}
    
        constexpr auto operator*() const noexcept -> TEnumeration { return value; }
        constexpr auto operator-> () const & noexcept -> const TEnumeration* {
          return &value;
        }
        constexpr auto operator++() & noexcept -> Iterator {
          value = static_cast<TEnumeration>(1 + static_cast<type>(value));
          return *this;
        }
    
        [[nodiscard]] constexpr auto operator==(Iterator i) -> bool { return i.value == value; }
        [[nodiscard]] constexpr auto operator!=(Iterator i) -> bool { return i.value != value; }
      };
    
      constexpr auto begin() const noexcept -> Iterator { return Iterator{}; }
      constexpr auto cbegin() const noexcept -> Iterator { return Iterator{}; }
    
      constexpr auto end() const noexcept -> Iterator { return Iterator{TSize}; }
      constexpr auto cend() const noexcept -> Iterator { return Iterator{TSize}; }
    
      [[nodiscard]] constexpr auto size() const noexcept -> type {
        return static_cast<type>(TSize);
      }
    };
    

    The usage:

    #include <iostream>
    int main(int, char**) {
      auto range = EnumRange<Vehicle>{};
      std::cout << static_cast<int>(range.size()) << std::endl;
      for (auto v : range) std::cout << to_string(v) << std::endl;
    }
    

    As you saw in the first test code, you can go from a numeric value to an enumeration by using static_cast. However, it assumes that you have some value that is valid for the enumeration. With the same assumptions of the range, we can write our own checked variant:

    #include <stdexcept>
    #include <type_traits>
    
    template <typename TEnumeration, TEnumeration TSize = TEnumeration::SIZE>
    [[nodiscard]] constexpr auto checked_enum_cast(
        std::underlying_type_t<TEnumeration> numeric_value) noexcept(false)
        -> TEnumeration {
            using type = std::underlying_type_t<TEnumeration>;
      if constexpr (std::is_signed_v<type>)
        if (numeric_value < 0) throw std::out_of_range{"Negative value"};
    
      if (numeric_value >= static_cast<type>(TSize)) throw std::out_of_range{"Value too large"};
    
      return static_cast<TEnumeration>(numeric_value);
    }
    

    To use this, you can write:

      try {
        std::cout << to_string(checked_enum_cast<Vehicle>(1)) << std::endl;
        std::cout << to_string(checked_enum_cast<Vehicle>(2)) << std::endl;
      } catch (const std::out_of_range& e) {
        std::cout << e.what() << std::endl;
      }
    

    Note: If one would live in an exception-free world, one could return std::nullopt and change the return type to std::optional<TEnumeration> instead.

    All code combined + execution at Compiler Explorer

    Please note that the iterator can be refined, however, I ain't an expert in the details. (and for looping, it doesn't matter, if you ever want to use it for an algorithm it could)

    0 讨论(0)
  • 2020-12-06 23:48

    In C++ a class has to be created:

    class TYPE 
    {
    public:
        static const TYPE AUTO;
        static const TYPE MOTOCYCLE;
    
    private:
        std::string mBrandName;
    
        TYPE(std::string iBrandName)
            : mBrandName(iBrandName)
        {}
    
        TYPE(const TYPE&) = default;
        TYPE(TYPE&&)      = default;
        TYPE& operator=(const TYPE&) = default;
        TYPE& operator=(TYPE&&) = default;
        ~TYPE() = default;
    
    public:
        std::string getBrandName() { return mBrandName; }
    
        static TYPE getMotocycle() { return MOTOCYCLE; }
        static TYPE getAuto() { return AUTO; }
    };
    
    const TYPE TYPE::AUTO("BMW");
    const TYPE TYPE::MOTOCYCLE("Kawasaki");
    

    But this doesn't have the benefits of an enum (automatic numbering, ordering, conversions,...)

    0 讨论(0)
提交回复
热议问题