Должен ли компилятор выдавать предупреждение, если метки переименования не соответствуют типу?

Я бы хотел, чтобы компилятор выдавал предупреждение:

"Банан - это не цвет".

Я понимаю, что в контексте оператора switch ярлыки продвигаются до int, компилятор доволен 0, и ему все равно, является ли он "зеленым" или "бананом".

Я надеялся, что конверсия для GCC сделает трюк.

enum Color
  {
    Green = 0
  };

enum Fruit
  {
    Banana = 0
  };

int main()
{
  Color c = Green;
  switch (c)
    {
    case Banana:
      std::cerr << "Banana" << std::endl;
      break;
    }
  return 0;
}

Ответ 1

Сильно типизированные перечисления:

С++ 11 вводит строго типизированный enum s, используя enum class:

#include <iostream>

enum class Color
  {
    Green = 0
  };

enum class Fruit
  {
    Banana = 0
  };


int main() {
  Color c = Color::Green;
  switch (c)
    {
    case Fruit::Banana:
      std::cerr << "Banana" << std::endl;
      break;
    }
  return 0;

}

Этот код завершится неудачно, как вы надеялись:

test.cc:18:17: error: не удалось преобразовать '(Fruit) 0' из 'Fruit' в 'Color'

Примечание. enum class больше не приводит к Green и Banana в пространство имен, поэтому вы должны явно писать Color:: и Fruit:: сейчас, но вы также получаете типы безопасности.


Проблемы с предупреждением в С++ 03

Я не думаю, что предупреждение об этом в С++ 03 имеет смысл, это в основном просто станет шумом.

Люди часто используют enum как константы времени компиляции, даже для таких вещей, как бит-поля. Чтобы предупреждение было значимым, вам придется ловить такие вещи, как enum { foo=0xf }; int c = foo;, и многие кодовые базы разбросаны с помощью преобразований int/enum. (Позволяя этому победить точку любой более сильной проверки типа).

Хуже, хотя будет enum использоваться практически в любом виде метапрограммного контекста, где анонимный enum не только используется взаимозаменяемо с типами int на регулярной основе:

template <int I>
struct is_odd {
  enum { value = !(I % 2) };
};

template <int I>
struct foo {
  static void bar() { /* I is true */ }
};

template <>
struct foo<0> {
  static void bar() { /* I is false */ }
};

int main() {
  foo<is_odd<201>::value>::bar();
  int i = is_odd<200>::value;
}

но они также используются рекурсивно в качестве локального хранилища:

template <int N> 
struct factorial {
    enum {
        // This enum is *not* compatible with the one in N-1
        value = N * factorial<N - 1>::value
    };
};

template <> 
struct factorial<0> {
    enum { value = 1 };
};

Это является частью причины, по которой enum class требовался, чтобы ввести неразрывный способ добавления безопасности типа в текущее состояние enum в С++. Было бы так много предупреждений из существующего кода, что предупреждение будет рядом с бесполезным из-за таких вещей.

Даже в довольно простом заявлении оператора switch, которое вы показали, что-то вроде этого законно:

#include <iostream>
enum Color { Green = 0x1, Red = 0x2 };
enum Fruit { Banana = 0x3 };

int main() {
  int v = Green|Red;
  Color c = Color(v);
  switch (c) {
  case Banana:
    std::cerr << "Banana" << std::endl;
    break;
  }
  return 0;
}

Хотя это законно здесь, это не очень значимо, но такие вещи используются довольно регулярно и значимо в "бит-скрипичном" C-коде еще. Точка этого примера состоит в том, что, разрешая преобразование intenum в любом месте, это эффективно означает, что строгое описание типа enum позже становится бессмысленным. В общем случае вы не можете определить, произошло ли подобное преобразование (возможно, это было в другой единицы перевода).

enum class, безусловно, является самым лучшим способом введения такой строгости без ущерба для существующего кода.

Ответ 2

В С++ 11 (новый стандарт С++) вы можете использовать enum class для создания строго типизированного перечисления. См. Статью Wikipedia на С++ 11.

Пример:

enum class Color { Green };
enum class Fruit { Banana };

// Put your main here - it would now fail