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

余生颓废 提交于 2020-02-24 20:59:49

问题


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

Example

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

    private String mBrandName = "";

    TYPE(final String iBrandName)
    {
        mBrandName = iBrandName;
    }

    public String getBrandName()
    {
        return mBrandName;
    }

    static TYPE getMotocycle()
    {
        return MOTOCYCLE;
    }

    static TYPE getAuto()
    {
        return AUTO;
    }
}

Usage

String motoBrand = TYPE.getMotocycle().getBrandName(); // == BMW
String autoBrand = TYPE.getAuto().getBrandName(); // == Kawasaki

So, idea is that you can give to constructor specific value (int, String whatever) and then get it. So, you have order number as well as specific value that you set to...

Question is, from documentation https://docs.microsoft.com/en-us/cpp/cpp/enumerations-cpp?view=vs-2019 I understood that there is no such option in cpp, is it right?

P.S. Reason why I need enum, because you save all enum functionality(like count of elements or get element by number) and additionally you get a little bit more with constructor.

In Java I can get count of elements this way TYPE.values().length https://stackoverflow.com/a/17492102/5709159

In Java I can get element by number this way TYPE.values()[ordinal] https://stackoverflow.com/a/609866/5709159


回答1:


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)




回答2:


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,...)



来源:https://stackoverflow.com/questions/57346118/enum-class-constructor-c-how-to-pass-specific-value

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!