0

Could someone help with the following compiler error? I am trying to create a Java like enums in C++ and I am getting compiler errors in Visual Studio 2015.

I am using these Java like Enum classes as members of a struct and I would like the sizeof(MessageType) to be the same as the Value type. I know that this will require that I remove the mValueStr member from the class, however then I will not be able to look up the value from a string. Any suggestions on how I might be able to achieve that would be greatly appreciated.

One of the really neat things in the Java Enums is the ability to look up the enum type using the String name or value. To that end I added 2 methods to my enum classes to reverse look up the enums from these strings or values. Unfortunately the method below has the squiggly lines indicated

static MessageType valueOf(const int& rVal) {
    for (const auto& next : getValues()) {
        if (next == rVal) {
                 ~~
            return next;
        }
    }
    throw std::invalid_argument(
        "Illegal Argument: " + rVal);
}

and the compiler complains as follows and I cannot quite figure it out:

1>c:\main\dlmu\caclient\udpclient\cmc.h(123): error C2678: binary '==': no operator found which takes a left-hand operand of type 'const MessageType' (or there is no acceptable conversion)
1>  c:\main\dlmu\caclient\udpclient\cmc.h(123): note: could be 'built-in C++ operator==(MessageType::Value, int)'
1>  c:\main\dlmu\caclient\udpclient\cmc.h(123): note: while trying to match the argument list '(const MessageType, const int)'
1>  UDPClient.cpp
1>c:\main\dlmu\caclient\udpclient\cmc.h(123): error C2678: binary '==': no operator found which takes a left-hand operand of type 'const MessageType' (or there is no acceptable conversion)
1>  c:\program files (x86)\windows kits\8.1\include\shared\guiddef.h(192): note: could be 'bool operator ==(const GUID &,const GUID &)'
1>  c:\main\dlmu\caclient\udpclient\cmc.h(123): note: while trying to match the argument list '(const MessageType, const int)'

The key to getting this type of functionality working is to use the integral cast operator to a Value type (which in this case is an e bit enum value)

class MessageType {
public:
    enum class Value : uint8_t {
        undefined = 0, membersystem = 10
    };
    static const MessageType Undefined, MemberSystem;
    // integral operator cast for switch statements (cast to named enum)
    inline operator const Value() const {
        return mValue;
    }
    // strict weak ordering for set ops
    inline bool operator<(const MessageType& rhs) const {
        return mValue < rhs.mValue;
    }
    // serialized version of the enum
    inline std::string getStringVal() const {
        return mStringVal;
    }
    static const std::set<MessageType>& getValues() {
        static std::set<MessageType> gValues;
        if (gValues.empty()) {
            gValues.insert(Undefined);
            gValues.insert(MemberSystem);
        }
        return gValues;
    }
    static MessageType valueOf(const int& rVal) {
        for (const auto& next : getValues()) {
            if (next == rVal) {
                return next;
            }
        }
        throw std::invalid_argument(
            "Illegal Argument: " + rVal);
    }
    static MessageType valueOf(const std::string& rStringVal) {
        for (const auto& next : getValues()) {
            if (next.getStringVal() == rStringVal) {
                return next;
            }
        }
        throw std::invalid_argument(
            "Illegal Argument: " + rStringVal);
    }
private:
    MessageType(const Value& rValue, const std::string& rStringVal)
        : mValue(rValue)
        , mStringVal(rStringVal)
    {}

    Value mValue;
    std::string mStringVal;
};

The really strange thing is that right before this definition I have a similar class that works fine without squiggly lines as follows:

class DiscreteStatus {
public:
    enum Value : uint8_t {
        normaloperation = 0, nocomputeddata = 1, functionaltest = 2, failurewarning = 3
    };
    static const DiscreteStatus NormalOperation, NoComputedData, FunctionalTest, FailureWarning;
    // integral operator cast for switch statements (cast to named enum)
    inline operator const Value() const {
        return mValue;
    }
    // strict weak ordering for set ops
    inline bool operator<(const DiscreteStatus& rhs) const {
        return mValue < rhs.mValue;
    }
    // serialized version of the enum
    inline std::string getStringVal() const {
        return mStringVal;
    }
    static const std::set<DiscreteStatus>& getValues() {
        static std::set<DiscreteStatus> gValues;
        if (gValues.empty()) {
            gValues.insert(NormalOperation);
            gValues.insert(NoComputedData);
            gValues.insert(FunctionalTest);
            gValues.insert(FailureWarning);
        }
        return gValues;
    }
    static DiscreteStatus valueOf(const int& rVal) {
        for (const auto& next : getValues()) {
            if (next == rVal) {
                return next;
            }
        }
        throw std::invalid_argument(
            "Illegal Argument: " + rVal);
    }
    static DiscreteStatus valueOf(const std::string& rStringVal) {
        for (const auto& next : getValues()) {
            if (next.getStringVal() == rStringVal) {
                return next;
            }
        }
        throw std::invalid_argument(
            "Illegal Argument: " + rStringVal);
    }
private:
    DiscreteStatus(const Value& rValue, const std::string& rStringVal)
        : mValue(rValue)
        , mStringVal(rStringVal)
    {}

    Value mValue;
    std::string mStringVal;
};

The implementation file (CPP) performs the static initialization as follows:

const DiscreteStatus DiscreteStatus::NormalOperation(DiscreteStatus::Value::normaloperation, "NML");
const DiscreteStatus DiscreteStatus::NoComputedData(DiscreteStatus::Value::nocomputeddata, "NCD");
const DiscreteStatus DiscreteStatus::FunctionalTest(DiscreteStatus::Value::functionaltest, "FT");
const DiscreteStatus DiscreteStatus::FailureWarning(DiscreteStatus::Value::failurewarning, "FW");

const MessageType MessageType::Undefined(MessageType::Value::undefined, "Undefined");
const MessageType MessageType::MemberSystem(MessageType::Value::membersystem, "MemberSystem");
johnco3
  • 2,401
  • 4
  • 35
  • 67

1 Answers1

4

In both cases, the intention is to call the type-conversion operator in order to perform a conversion to the corresponding Value type.

In the first case, you have Value defined like this:

enum class Value : uint8_t

This does not work like public inheritance but is more similar to private inheritance: it's an implemented-in-terms-of relationship. Client code must not and in your case cannot rely on the fact that Value is internally implemented as a uint8_t.

In the second case, however, it's as follows:

enum Value : uint8_t

This can be pictured as something like public inheritance, meaning that client code can rely and depend on the fact that Value is really an uint8_t. The type conversion thus works.

Now, you could quick-fix your code by changing the first definition to enum Value : uint8_t, but one has to wonder why you want to provide a valueOf function with an integer argument anyway. It breaks the abstraction.


Generally, I strongly advise against this whole approach. It seems overly complicated, as is always the case when one attempts to write language A in language B.

In this particular case, any similarities between Java enums and C++11 enum classes are only superficial. At the end of the day, from a C++ perspective, Java enums are non-copyable classes with static constant pointers to garbage-collected instances which cannot be dereferenced. You do not want to emulate all of this in C++, nor can you, nor should you.

Christian Hackl
  • 27,051
  • 3
  • 32
  • 62
  • that was a great answer!! Thanks, The reason I need to have the valueOf (const int& rValue) method is to deserialize these enum like classes from bytes sent over the wire, these are part of a higher level structure (who's field is actually the Value type (in this case a uint8_t) right now though the sizeof (DiscreteStatus) and sizeof (MessageStatus) which should be 1 are not as there is an embedded string field which I am trying to remove, great answer my friend – johnco3 Jan 13 '16 at 22:04