2

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://learn.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

Sirop4ik
  • 4,543
  • 2
  • 54
  • 121
  • 1
    It is possible to implement similar functionality with regular class. – user7860670 Aug 04 '19 at 11:26
  • 4
    enum class is not a class, it's just a strongly typed enum. – mfnx Aug 04 '19 at 11:27
  • 1
    Just use a class, this kind of Java enum is basically a class already anyway. – Hatted Rooster Aug 04 '19 at 11:30
  • 4
    `enum` does not have *"count of elements or get element by number"* functionality – user7860670 Aug 04 '19 at 11:59
  • @VTT there is such functionality. edited my question – Sirop4ik Aug 04 '19 at 12:47
  • 1
    C++ enum are not the same as a Java enum. To get the same kind of functionality in C++, you'd need to use a class or struct, which might also have a C++ enum embedded in it (if that is helpful for one's specific use case). – Eljay Aug 04 '19 at 12:47
  • 1
    "In Java I can ..." - Well, C++ is *not* Java – Jesper Juhl Aug 04 '19 at 12:48
  • 2
    Perhaps you are getting confused by `enum` keyword. C++ `enum` (or `enum class`) has almost nothing in common with Java `enum`. C++ `enum` is essentially just a separate integer type optionally with some well-known constant values. – user7860670 Aug 04 '19 at 12:51
  • @VTT ok, I understood. Thanks – Sirop4ik Aug 04 '19 at 12:57
  • As a note, a Java `enum` is actually somewhat more similar to a hybrid of an array and one of C++'s `map` types (or to an array of some two-field `struct` containing a key field and a value field) than to a C or C++ `enum`, in that it maps values to distinct keys, while also giving each a distinct numerical index. (Where, for comparison, C and C++ `enum` and `enum class` conflate the value with the numerical index.) – Justin Time - Reinstate Monica Aug 04 '19 at 13:23
  • An artical in Dr. Dobb's [When enum Just Isn't Enough: Enumeration Classes for C++](http://www.drdobbs.com/when-enum-just-isnt-enough-enumeration-c/184403955?pgno=1) has a more Java-like C++ enum. – Eljay Aug 04 '19 at 16:14

2 Answers2

4

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)

JVApen
  • 11,008
  • 5
  • 31
  • 67
  • Note: As I'm not familiar with Java enumerations, I might have missed some functionality. If so, please let me know what is missing, than I'll update the answer. – JVApen Aug 04 '19 at 13:40
0

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

Robert Andrzejuk
  • 5,076
  • 2
  • 22
  • 31
  • maybe a better solution would be to wrap an enum in a class? – mfnx Aug 04 '19 at 12:45
  • Ok, but how do you can for example get number of types? (AUTO, MOTO is 2) – Sirop4ik Aug 04 '19 at 12:48
  • First of all this example won't compile. It is illegal to initialize static class fields of parent type in class. @AlekseyTimoshchenko You will need to implement all that functionally manually. – user7860670 Aug 04 '19 at 12:54