"control достигает конца не-void функции" с полностью обработанным переключателем случая над типом перечисления

Почему этот код вызывает "управление достигает конца не пустой функции", даже если обрабатываются все возможные значения type_t? Как лучше всего позаботиться об этом предупреждении? Добавление return -1 после переключения?
(Код протестирован здесь)

typedef enum {
    A,
    B
} type_t;

int useType(type_t x) {
    switch (x) {
        case A:
            return 0;
        case B:
            return 1;
    }
}


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

Ответ 1

В общем случае enum не являются исключительными. Кто-то может назвать вашу функцию, например, useType( (type_t)3 );. Это упоминается специально в С++ 14 [dcl.enum]/8:

Можно определить перечисление, которое имеет значения, не определенные каким-либо из его счетчиков.

Теперь существует множество правил о том, какие другие значения возможны для других типов перечислений.

Существует две категории перечисления. Первый - фиксированный базовый тип, например. enum type_t : int или enum class type_t. В этих случаях все значения базового типа являются допустимыми перечислениями.

Второй не является фиксированным базовым типом, который включает в себя pre-С++ 11 перечисления, такие как ваши. В этом случае правило о значениях можно суммировать, сказав: вычислить наименьшее количество бит, необходимое для хранения всех значений перечисления; то любое число, выражаемое в этом количестве битов, является допустимым значением.


Итак, в вашем конкретном случае один бит может содержать оба значения A и B, поэтому 3 недопустимое значение для счетчика.

Но если ваше перечисление было A,B,C, то даже если 3 не указано конкретно, это правильное значение приведенным выше правилом. (Таким образом, мы видим, что почти все перечисления не будут эксклюзивными).

Теперь нам нужно посмотреть правило о том, что произойдет, если кто-то действительно попытается преобразовать 3 в type_t. Правило преобразования - С++ 14 [expr.static.cast]/10, в котором говорится, что создается неопределенное значение.

Однако CWG issue 1766 признал, что текст С++ 14 был дефектным и заменил его следующим:

Значение интегрального или перечисляемого типа может быть явно преобразовано в полный тип перечисления. Значение не изменяется, если исходное значение находится в диапазоне значений перечисления (7.2). В противном случае поведение undefined.

Поэтому в вашем конкретном случае ровно два счетчика со значением 0 и 1, никакое другое значение не возможно, если программа уже вызвала поведение undefined, поэтому предупреждение можно считать ложным положительным.


Чтобы удалить предупреждение, добавьте случай default:, который что-то делает. Я также хотел бы предложить в интересах защитного программирования, что в любом случае неплохо иметь случай по умолчанию. На практике это может служить "сдерживанием" поведения undefined: если кто-то действительно имеет недопустимое значение, то вы можете просто выбросить или прервать его.


NB: Что касается самого предупреждения: невозможно, чтобы компилятор точно предупреждал, когда и только если поток управления достигнет конца функции, потому что это потребует решения проблемы с остановкой.

Они склонны ошибаться со стороны осторожности: компилятор будет предупреждать, если он не совсем уверен, что означает ложные срабатывания.

Таким образом, наличие этого предупреждения необязательно указывает на то, что исполняемый файл фактически разрешил вход в путь по умолчанию.

Ответ 2

Если вы абсолютно уверены, что x никогда не будет чем-то отличным от A или B (например, если это закрытый член класса), вы можете использовать assert:

int useType(type_t x) {
    switch (x) {
        case A:
            return 0;
        case B:
            return 1;
    }
    assert(false);
}