web-dev-qa-db-fra.com

Masques de bit conformes C ++ 11 standard utilisant la classe enum

Pouvez-vous implémenter des masques de bit sécurisés de type conforme (comme décrit au 17.5.2.1.3 du projet n3242) en utilisant la classe enum? D'après ma lecture, un type T est un masque de bits s'il prend en charge les opérateurs |, &, ^, ~, | =, & = et ^ = et vous pouvez faire plus si (l & r) où l et r sont de type T L'opérateur! = Et == manque à la liste et pour permettre le tri, on veut probablement aussi surcharger <.

Faire fonctionner les opérateurs est juste un code passe-partout ennuyeux mais je ne vois pas comment faire si (l & r). Au moins, ce qui suit ne compile pas avec GCC (en plus d'être extrêmement dangereux car il permet une conversion implicite erronée en int):

enum class Foo{
    operator bool(){
        return (unsigned)*this;
    }
};

EDIT: Je sais maintenant avec certitude que les classes enum ne peuvent pas avoir de membres. La vraie question comment faire si (l & r) reste cependant.

32
B.S.

Je pense que vous pouvez ... Vous devrez ajouter des opérateurs pour les choses bitmasky. Je ne l'ai pas fait ici mais vous pourriez facilement surcharger n'importe quel opérateur relationnel.

  /**
   *
   */
  // NOTE: I changed to a more descriptive and consistent name
  //       This needs to be a real bitmask type.
  enum class file_permissions : int
  {
    no_perms        = 0,

    owner_read      =  0400,
    owner_write     =  0200,
    owner_exe       =  0100,
    owner_all       =  0700,

    group_read      =   040,
    group_write     =   020,
    group_exe       =   010,
    group_all       =   070,

    others_read     =    04,
    others_write    =    02,
    others_exe      =    01,
    others_all      =    07,

    all_all     = owner_all | group_all | others_all, // 0777

    set_uid_on_exe  = 04000,
    set_gid_on_exe  = 02000,
    sticky_bit      = 01000,

    perms_mask      = all_all | set_uid_on_exe | set_gid_on_exe | sticky_bit, // 07777

    perms_not_known = 0xffff,

    add_perms       = 0x1000,
    remove_perms    = 0x2000,
    symlink_perms   = 0x4000
  };

  inline constexpr file_permissions
  operator&(file_permissions x, file_permissions y)
  {
    return static_cast<file_permissions>
      (static_cast<int>(x) & static_cast<int>(y));
  }

  inline constexpr file_permissions
  operator|(file_permissions x, file_permissions y)
  {
    return static_cast<file_permissions>
      (static_cast<int>(x) | static_cast<int>(y));
  }

  inline constexpr file_permissions
  operator^(file_permissions x, file_permissions y)
  {
    return static_cast<file_permissions>
      (static_cast<int>(x) ^ static_cast<int>(y));
  }

  inline constexpr file_permissions
  operator~(file_permissions x)
  {
    return static_cast<file_permissions>(~static_cast<int>(x));
  }

  inline file_permissions &
  operator&=(file_permissions & x, file_permissions y)
  {
    x = x & y;
    return x;
  }

  inline file_permissions &
  operator|=(file_permissions & x, file_permissions y)
  {
    x = x | y;
    return x;
  }

  inline file_permissions &
  operator^=(file_permissions & x, file_permissions y)
  {
    x = x ^ y;
    return x;
  }
25
emsr

Je ne sais pas exactement quels sont vos critères d'acceptation, mais vous pouvez simplement faire operator & retourne une classe wrapper avec les conversions appropriées et un explicit operator bool:

#include <type_traits>

template<typename T> using Underlying = typename std::underlying_type<T>::type;
template<typename T> constexpr Underlying<T>
underlying(T t) { return Underlying<T>(t); }

template<typename T> struct TruthValue {
    T t;
    constexpr TruthValue(T t): t(t) { }
    constexpr operator T() const { return t; }
    constexpr explicit operator bool() const { return underlying(t); }
};

enum class Color { RED = 0xff0000, GREEN = 0x00ff00, BLUE = 0x0000ff };
constexpr TruthValue<Color>
operator&(Color l, Color r) { return Color(underlying(l) & underlying(r)); }

Tous vos autres opérateurs peuvent continuer à renvoyer Color, bien sûr:

constexpr Color
operator|(Color l, Color r) { return Color(underlying(l) | underlying(r)); }
constexpr Color operator~(Color c) { return Color(~underlying(c)); }

int main() {
    constexpr Color YELLOW = Color::RED | Color::GREEN;
    constexpr Color WHITE = Color::RED | Color::GREEN | Color::BLUE;
    static_assert(YELLOW == (WHITE & ~Color::BLUE), "color subtraction");
    return (YELLOW & Color::BLUE) ? 1 : 0;
}
9
ecatmur

Les énumérations étendues (celles créées avec enum class Ou enum struct) Ne sont pas des classes. Ils ne peuvent pas avoir de fonctions membres, ils fournissent simplement des énumérateurs fermés (non visibles au niveau de l'espace de noms).

L'opérateur est absent de la liste! = Et ==

Ces opérateurs sont déjà pris en charge par les types d'énumération, les types entiers et std::bitset, Il n'est donc pas nécessaire de les surcharger.

et pour permettre le tri, on veut probablement aussi surcharger <.

Pourquoi voulez-vous trier les bitmasks? (A | b) est-il supérieur à (a | c)? std::ios::in Est-il inférieur à std::ios::app? Est-ce que ça importe? Les opérateurs relationnels sont toujours définis pour les types d'énumération et les types entiers de toute façon.

Pour répondre à la question principale, vous implémenteriez & Comme une fonction non-membre surchargée:

Foo operator&(Foo l, Foo r)
{
    typedef std::underlying_type<Foo>::type ut;
    return static_cast<Foo>(static_cast<ut>(l) & static_cast<ut>(r));
}

Je crois que toutes les opérations requises pour les types de masques de bits pourraient être définies pour les énumérations étendues, mais pas les exigences telles que

Ci & Cj est différent de zéro et Ci & Cj est nul

et:

La valeur Y est définie dans l'objet [~ # ~] x [~ # ~] est l'expression X & Y est différent de zéro.

Étant donné que les énumérations étendues ne prennent pas en charge la conversion implicite en types entiers, vous ne pouvez pas tester de manière fiable si elle est non nulle ou non. Vous auriez besoin d'écrire if ((X&Y) != bitmask{}) et je ne pense pas que ce soit l'intention du comité.

(Au début, je pensais qu'ils pouvaient être utilisés pour définir des types de masques de bits, puis je me suis souvenu que j'avais essayé d'en implémenter un à l'aide d'énumérations de portée et rencontré le problème de test pour zéro/non nul.)

Edit: Je viens de me rappeler que std::launch Est un type d'énumération de portée et un type de masque de bits ... donc les énumérations de portée apparemment peuvent être des types de masque de bits!

3
Jonathan Wakely

Un court exemple d'enum-flags ci-dessous.

#indlude "enum_flags.h"

ENUM_FLAGS(foo_t)
enum class foo_t
    {
     none           = 0x00
    ,a              = 0x01
    ,b              = 0x02
    };

ENUM_FLAGS(foo2_t)
enum class foo2_t
    {
     none           = 0x00
    ,d              = 0x01
    ,e              = 0x02
    };  

int _tmain(int argc, _TCHAR* argv[])
    {
    if(flags(foo_t::a & foo_t::b)) {};
    // if(flags(foo2_t::d & foo_t::b)) {};  // Type safety test - won't compile if uncomment
    };

ENUM_FLAGS (T) est une macro, définie dans enum_flags.h (moins de 100 lignes, libre d'utilisation sans aucune restriction).

2
Yuri Yaryshev