使枚举值与许多其他枚举值相等



我需要使用一个包含各种值的枚举,在本例中是各种建筑部件。大多数都是唯一的,但有一些我希望是等价的。我的意思是:

enum class EPiece: uint8 {
    Ceiling,
    Table,
    Door,
    WestWall,
    NorthWall,
    SouthWall,
    EastWall,
    Wall,
    Floor
};

我想让Wall == WestWall变成true,以及Wall == NorthWall,等等。然而,WestWall == NorthWall是假的。

为什么我这样做是因为我正在制作一款游戏,其中各种部件都有一个基于它们是什么/它们在哪里的定义。玩家必须按照预先设定的顺序放置各种棋子。玩家首先必须放置NorthWall棋子。他们将有各种可用的棋子,必须选择一个Wall棋子,并试图将其放置在NorthWall棋子上。游戏检查两者是否相等(在本例中为true),以及当前要放置的棋子是否为NorthWall。如果他们试图把它放在WestWall片上,它应该会失败,因为它还没有到那个阶段。

我想通过标记来实现,比如

WestWall = 0x01,
NorthWall = 0x02,
SouthWall = 0x04,
EastWall = 0x08,
Wall = WestWall | NorthWall | SouthWall | EastWall

和检查,执行如下操作:

// SelectedPiece is the Piece the Player selected and is attempting to place
// PlacedOnPiece is the Piece that we are attempting to place on top of
// CurrentPieceToPlace is what Piece we are supposed to place at this stage
if ((CurrentPieceToPlace == PlacedOnPiece) && (SelectedPiece & PlacedOnPiece != 0)) {
}

问题是,我有很多零件,我的理解是,要让这些旗帜起作用,我必须使用2的幂。这意味着如果我使用uint32,我最多可以有32个棋子,我不想受此限制。我可能只需要20个左右,但我不想被困住。

有什么建议吗?此时我需要使用enum,所以我不能尝试其他类型。

我建议不要重载==来实现这个含义。==通常是可传递的(如果A==B和B==C,那么A==C),如果它不能传递,否则"理智"的代码将会崩溃。

从enum开始:

enum class EPiece: uint8 {
  Ceiling,
  Table,
  Door,
  WestWall,
  NorthWall,
  SouthWall,
  EastWall,
  Wall,
  Floor
};

现在定义一个can_be_used_as_a关系。

bool can_be_used_as_a( EPiece x, EPiece used_as_a_y ) {
  if (x==y) return true;
  switch(x) {
    case Wall: {
      switch(used_as_a_y) {
        case WestWall:
        case EastWall:
        case NorthWall:
        case EastWall:
          return true;
        default: break;
      }
    }
    default: break;
  }
  switch(used_as_a_y) {
    case Wall: {
      switch(x) {
        case WestWall:
        case EastWall:
        case NorthWall:
        case EastWall:
          return true;
        default: break;
      }
    }
    default: break;
  }
  return false;
}

现在can_be_used_as_a( WestWall, Wall )true,因为WestWall可以用作Wall。同理,Wall也可以作为WestWall使用。但是WestWall不能作为EastWall使用。

如果您希望语法更简洁一些,可以编写一个命名操作符:

namespace named_operator {
  template<class D>struct make_operator{make_operator(){}};
  template<class T, char, class O> struct half_apply { T&& lhs; };
  template<class Lhs, class Op>
  half_apply<Lhs, '*', Op> operator*( Lhs&& lhs, make_operator<Op> ) {
    return {std::forward<Lhs>(lhs)};
  }
  template<class Lhs, class Op, class Rhs>
  auto operator*( half_apply<Lhs, '*', Op>&& lhs, Rhs&& rhs )
  -> decltype( invoke( std::forward<Lhs>(lhs.lhs), Op{}, std::forward<Rhs>(rhs) ) )
  {
    return invoke( std::forward<Lhs>(lhs.lhs), Op{}, std::forward<Rhs>(rhs) );
  }
}

表示12行命名操作符库,如:

struct used_as_a_tag{};
static const named_operator::make_operator<used_as_a_tag> can_use_as_a;
bool invoke( EPiece x, used_as_a_tag, EPiece y ) {
  return can_be_used_as_a(x,y);
}

现在我们可以这样做:

if (x *can_use_as_a* y) {
}

,操作符出现在左右操作数之间。但这可能太过分了。

最后,考虑使用enum class代替enum

你的方向是对的。你拥有的每一种墙壁类型都代表一个比特,这非常棒。现在您所要做的就是将它们合并到Wall中,并在检查中提取它们,因此:

WestWall = 0x01,  //0b0001
NorthWall = 0x02, //0b0010
SouthWall = 0x04, //0b0100
EastWall = 0x08,  //0b1000
Wall =    0xF  //0b1111 

现在,要检查枚举中的一个值是否代表另一个值,您应该这样写:

bool isSame(EPiece first, EPiece second)
{
    //if they are the same, they are, well... the same.
    if(first == second)
        return true;
    //this only leaves the bits that are present in both values, so 
    //if the result is different from 0, then second is a part of first, so
    //we return true
    else if(first & second)
        return true;
    //if we are here, then first and second are unrelated
    return false;
}

您可以定义自己的比较操作符,如下所示:

bool operator==(EPiece lhs, EPiece rhs)
{
    if (int(lhs) == int(EPiece::Wall) &&
         (int(rhs) == int(EPiece::NorthWall) ||
          int(rhs) == int(EPiece::SouthWall))) // lots more cases...
    {
        return true;
    }
    return int(lhs) == int(rhs);
}

请注意,上面的声明(虽然不一定是定义)必须在您希望比较这些内容的任何地方可见,因此您应该在enum声明旁边声明它。

这里有两种稍微不同的可能性:

enum {
    Flag0 = 1 << 0,
    Flag1 = 1 << 1,
    Flag2 = 1 << 2,
    Flag3 = 1 << 3,
    FlagMask = 0x07
}
if (value & FlagMask) // it's got some flags
    { ... }
if (value & Flag3) // Flag3
    { ... }

enum {
    ItemA0,
    ItemABegin = ItemA0,
    ItemA1,
    ItemA2,
    // insert ItemAs here
    ItemAEnd,
    ItemB0,
    ItemBBegin = ItemB0,
    ItemB1,
    // insert ItemBs here
    ItemBEnd,
}
if (ItemABegin <= value && value < ItemAEnd) // it's some ItemA
    { ... }
if (ItemBBegin <= value && value < ItemBEnd) // it's some ItemB
    { ... }
switch (value) { // switch on specific types
    case ItemB0: ... break;
    case ItemB1: ... break;
}

第二个版本仍然封装了枚举类型的思想。

最新更新