Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Overloading cast operator for enum class

In my project, I'm using multiple enum classes which I need to easily cast between depending on where I need to use them. They basically describe the same thing but are named differently to keep the code more easier to work with. Here are the enum classes:

enum class tetroType {
    None, I, O, T, J, L, S, Z
};

enum class gridBlock {
    Empty, Blue, Green, Orange, Purple, Red, Teal, Yellow
};

Each value in tetroType corresponds to a value in gridBlock (f. e. tetroType::I = gridBlock::Teal), but the first holds the information about the shape of tetronimo (in tetronimo class) and the second holds information about color of blocks (in grid class). I know I could just use one enum instead but this way you don't loose any information. Also I'd need to cast it into string if possible. This is how I would like to use it:

gridBlock grid = (gridBlock)tetroType::I;
string texture = (string)grid;

Right now the way I've got it set up is like this. I'm using this switch in the middle of other methods whenever I need to convert one enum into another or into a string:

switch (type) {
case tetroType::I:
    block = gridBlock::Teal;
    break;
case tetroType::O:
    block = gridBlock::Yellow;
    break;
case tetroType::T:
    block = gridBlock::Purple;
    break;
case tetroType::J:
    block = gridBlock::Blue;
    break;
case tetroType::L:
    block = gridBlock::Orange;
    break;
case tetroType::S:
    block = gridBlock::Green;
    break;
case tetroType::Z:
    block = gridBlock::Red;
    break;
case tetroType::None:
    block = gridBlock::Empty;
}
like image 585
Maroš Beťko Avatar asked Feb 10 '17 18:02

Maroš Beťko


3 Answers

You should look into overloading the enum class as an integer (this is a C++ 11 feature).

enum class tetroType : int {
    I = 1, O = 2, T = 3, J = 4, L = 5, S = 6, Z = 7, NONE
};

enum class gridBlock : int {
    Blue = 1, Green = 2, Orange = 3, Purple = 4, Red = 5, Teal = 6, Yellow = 9, EMPTY
};

From here you can write a basic conversion using ether C Style typecasting, or static_cast

gridBlock ConvertTetro(tetroType type){
    return static_cast<gridBlock>(static_cast<int>(type));
}

gridBlock ConvertTetro(tetroType type){
    return (gridBlock)((int)((type)));
}

this will match any grid block to equal tetrotypes, and will default to gridBlock::EMPTY if theres no matching type. This function should be pretty easy to figure out how to go the other way if needed. From here you need to match up the int values between the two.

You can also use char values as char literals ('A', 'b', '!') using

enum class enumName : char

This will work as long as the two underlying types

like image 109
Terryn Avatar answered Oct 20 '22 21:10

Terryn


Simple answer: Dont use enum class, use normal enum instead, they can be implicitly cast to their underlying type (default int).

In C++ 11, enum class is a strong alias which HAS to be casted from.

like image 9
Gambit Avatar answered Oct 20 '22 23:10

Gambit


If you accept the fact that you must keep the definition of one of the two enum coherent to the other, so that each gridBlock takes a value from tetroType then you can override operator== to use them seamlessly in comparisons and override a different operator (eg <<=) to mimic assignment between different types.

Something like this:

#include <iostream>
#include <type_traits>
#include <cassert>

using namespace std;

enum class tetroType {
    None, I, O, T, J, L, S, Z
};

enum class gridBlock {
    Empty = static_cast<std::underlying_type<tetroType>::type>(tetroType::None), 
    Blue = static_cast<std::underlying_type<tetroType>::type>(tetroType::I), 
    Green = static_cast<std::underlying_type<tetroType>::type>(tetroType::O), 
    Orange = static_cast<std::underlying_type<tetroType>::type>(tetroType::T), 
    Purple = static_cast<std::underlying_type<tetroType>::type>(tetroType::J), 
    Red = static_cast<std::underlying_type<tetroType>::type>(tetroType::L), 
    Teal = static_cast<std::underlying_type<tetroType>::type>(tetroType::S), 
    Yellow = static_cast<std::underlying_type<tetroType>::type>(tetroType::Z)
};

bool operator==(const tetroType& t, const gridBlock& g) { return static_cast<gridBlock>(t) == g; }
bool operator==(const gridBlock& g, const tetroType& t) { return static_cast<gridBlock>(t) == g; }

bool operator!=(const tetroType& t, const gridBlock& g) { return static_cast<gridBlock>(t) != g; }
bool operator!=(const gridBlock& g, const tetroType& t) { return static_cast<gridBlock>(t) != g; }

gridBlock& operator<<=(gridBlock& g, tetroType t) { g = static_cast<gridBlock>(t); return g; }
tetroType& operator<<=(tetroType& t, gridBlock g) { t = static_cast<tetroType>(g); return t; }

int main() {
  tetroType t1 = tetroType::I, t2 = tetroType::O;
  gridBlock g1 = gridBlock::Blue, g2 = gridBlock::Green;

  gridBlock g3;
  g3 <<= t1;
  tetroType t3;
  t3 <<= g2;


  assert(t1 == g1);
  assert(t1 != g2);
  assert(g3 == t1);
  assert(t3 == g2);

    return 0;
}

While this solution is concise, it's quite cryptic and obscure so you'd better document this behavior clearly. But in general with an enum class is quite safe to override operators like operator<<= since they are not defined in any case.

Mind that you can use a custom mapping between the values but if you don't need them, since you can initialize the values of one with the other, then there is no need to manually map them.

like image 1
Jack Avatar answered Oct 20 '22 21:10

Jack