Daniel Langr
Daniel Langr

Reputation: 23497

How not to compile casting into enum type in C++?

I have this "better" enum class that

  1. cannot contain invalid values, and
  2. cannot be used until enum value is not set explicitly,

as follows:

class Symmetry
{
    public:
        enum Type {
            GENERAL, SYMMETRIC, HERMITIAN,
            SKEW_SYMMETRIC, SKEW_HERMITIAN, UNINITIALIZED
        };

        Symmetry() { t_ = UNINITIALIZED }
        explicit Symmetry(Type t) : t_(t) { checkArg(t); }
        Symmetry& operator=(Type t) { checkArg(t); t_ = t; return *this; }

        operator Type() const {
            if (t_ == UNINITIALIZED) throw runtime_error("error");
            return t_;
        }

    private:
        Type t_;

        void checkArg(Type t) {
            if ((unsigned)t >= (unsigned)UNINITIALIZED)
                throw runtime_error("error");
        }
}; 

This allows me to write the following code:

Symmetry s1(Symmetry::SYMMETRIC);
Symmetry s2;
s2 = Symmetry::HERMITIAN;
Symmetry s3;
if (Symmetry::GENERAL == s3) // throws

My problem is that a compiler allows constructs such as:

Symmetry s1((Symmetry::Type)18); // throws
Symmetry s2;
s2 = (Symmetry::Type)18; // throws

I solved this problem by throwing exceptions, but I would prefer such a code not to compile at all (a compile time error). Is there a way how to manage this?

Upvotes: 2

Views: 517

Answers (3)

Fred Nurk
Fred Nurk

Reputation: 14212

No. If you allow any cast to be used, as your last example does, then there will always be some cast that can be used to subvert your type.

The solution is to not be in the habit of using these casts and to very suspiciously consider any code that uses these casts indiscriminately. View this type of casting as the nuclear bomb in your arsenal: it's important to have, but you always handle it with care and never want to deploy it more than rarely.

What warning options does your compiler have for casting? What lint tools are you using which may detect this misuse of casts?


That said, it appears you really want to hide the inner Type so users are less tempted to even use it. Realizing that, it's straight-forward to make that type name private, even while not preventing all cast misuse, by slightly tweaking your original:

struct Symmetry {
  enum {
    UNINITIALIZED,
    GENERAL, SYMMETRIC, HERMITIAN,
    SKEW_SYMMETRIC, SKEW_HERMITIAN
  };

private:
  typedef decltype(UNINITIALIZED) Hidden;
  Hidden _value;

public:
  Symmetry(Hidden value = UNINITIALIZED) : _value (value) {}
  Symmetry& operator=(Hidden value) { _value = value; return *this; }
  operator Hidden() const {
    if (_value == UNINITIALIZED) {
      throw std::logic_error("uninitialized Symmetry");
    }
    return _value;
  }

  bool initialized() const { return _value != UNINITIALIZED; }
  // required if you want to check for UNINITIALIZED without throwing in
  // the above conversion
};

This is a complete implementation, no details omitted or unknown, or issues with initialization order. The only caveat is decltype – with a pre-C++0x compiler, you'll have to use something implementation-specific or a library which wraps something implementation-specific.

And a smaller issue: change from runtime_error to logic_error, as using uninitialized values should be preventable beforehand and thus falls in the latter category.

Upvotes: 0

Sarfaraz Nawaz
Sarfaraz Nawaz

Reputation: 361322

My attempt using templates: (tested. However, this can be further improved!)

template<int N>
struct Symmetry  
{
    enum Type
    {
        GENERAL, SYMMETRIC, HERMITIAN,
        SKEW_SYMMETRIC, SKEW_HERMITIAN
    };
    template<Type e> struct allowed;
    template<> struct allowed<GENERAL>        { static const int value = GENERAL; };
    template<> struct allowed<SYMMETRIC>      { static const int value = SYMMETRIC; };
    template<> struct allowed<HERMITIAN>      { static const int value = HERMITIAN; };
    template<> struct allowed<SKEW_SYMMETRIC> { static const int value = SKEW_SYMMETRIC; };
    template<> struct allowed<SKEW_HERMITIAN> { static const int value = SKEW_HERMITIAN; };

    allowed<(Type)N> m_allowed;

    operator int()
    {
        return N;
    }
};

Symmetry<0> e0;                   //okay
Symmetry<1> e1;                   //okay
Symmetry<100> e4;                 //compilation error!
Symmetry<e0.SKEW_HERMITIAN> e3;   //okay
Symmetry<e0.SKEW_SYMMETRIC> e3;   //okay

Usage:

int main()
{
    Symmetry<0> e0;                   
    Symmetry<e0.HERMITIAN> e1;           

    switch (e1)
    {
    case e0.HERMITIAN:
        {
            cout << "It's working" << endl;
        }
        break;
    }
}

Upvotes: 1

Oliver Charlesworth
Oliver Charlesworth

Reputation: 272467

Potentially a crummy solution, but it would solve your immediate problem. Rather than having an inner enum type, define a little helper class with a private constructor, and make the outer class a friend. Then the "enum" values can be static const members in your outer class. Something like this:

(DISCLAIMER: untested, so there may be various compilation issues, but you should get the idea)

class Symmetry
{
public:
    class Type
    {
    private:
        Type() {};
        friend class Symmetry;
    };

    static const Type GENERAL;
    static const Type SYMMETRIC;
    static const Type HERMITIAN;
};

You will need some way of determining equality, but this should be fairly easy.

Upvotes: 4

Related Questions