Добавить атрибут [[strict]] в switch, и способ указать значение по-умолчанию в enum class

Игорь Савенков
Игорь Савенков

Представим что есть такой код:

enum class TestEnum {
    one,
    two
};

void someFunc() {
    TestEnum test = TestEnum::one;

    switch (test) {
    case TestEnum::one:
        cout << "One";
        break;
    case TestEnum::two:
        cout << "Two";
        break;
    default:
        cout << "Default";
        break;
    }
}

И вот что с ним не так:

1) Бесполезная ветка default. Если все работает как задумано, то эта ветка не должна выполняться вообще никогда.

2) Непонятки с самой последней инструкцией break; С виду она как-будто не нужна, но если ее удалить, а потом кто-то, например, из эстетических соображений перенесет default-ветку вверх switch-а то сразу возникнет ошибка из за пропавшего break. Вот код после этих 2х модификаций:

switch (test) {
default:
    cout << "Default";  // Error: break missing!
case TestEnum::one:
    cout << "One";
    break;
case TestEnum::two:
    cout << "Two";
    break;
}

3) Если удалить default ветку а затем добавить только в enum дополнительное значение "three", то программа без проблем скомпилируется, хотя в switch не перебраны все варианты enum-a и нет default ветки. Что тоже выглядит не так как задумано. Пример такой модификации:

enum class TestEnum {
    one,
    two,
    three
};

void someFunc() {
    TestEnum test = TestEnum::one;

    switch (test) {
    case TestEnum::one:
        cout << "One";
        break;
    case TestEnum::two:
        cout << "Two";
        break;
    } // Where is three?
}

4) Так же этот код возможно сломать, если убрать инициализацию переменной значением TestEnum::one . Тогда в switch будет выполняться default ветка, что тоже не выглядит как хорошее поведение для неинициализированной переменной.

Предлагаю следующее:

1) Добавить в enum class атрибут, который бы существенно ограничивал возможности создания такого enum-а без значения, например [[strict]]

2) Добавить в switch атрибут, например [[strict]], который бы гарантировал перебор всех значений enum ов и убирал нерабочие ветки:

Чтобы это сделать, нужно во-первых сделать break-поведение, поведением по-умолчанию. Т.е. мы убираем все break операторы и подразумеваем что в конце всех case будет автоматом происходить выходить из switch.
Во-вторых нужно гарантировать представленность всех случаев enum-а
Если параметром [[strict]] switch передали обычный enum то выдавать compilation error

Если параметром в [[strict]] switch передали enum class (enum, который без атрибута [[strict]]) то проверять, что обязательно есть default ветка, и обязательно есть все возможые case для значений этого enum, иначе compilation error.
default ветка как раз для случаев, когда этот enum class инициализирован мусором.
Если же нужен [[strict]] switch не по всем значениям enum class, то можно например добавить атрибут [[notfull]], тогда все скомпилится. Важно! атрибут [[notfull]] нужен только в комбинации [[strict]] switch + enum class (не строгий)

Если параметром в [[strict]] switch передали enum class [[strict]], то должно быть всего 2 возможных сценария, которые корректно скомпилируются: либо в этом switch укзааны все возможные значения enum и отсутствует default ветка, либо пропущен один или несколько вариантов enum и тогда default ветка гарантированно присутствует, иначе ошибка компиляции. Примеры кода:

enum TestEnum {
    one,
    two
};

enum class TestEnumClass {
    one,
    two
};

enum class [[strict]] TestEnumStrict {
    one,
    two
};

// строгие switch + обычные enum
    TestEnum test;
    [[strict]] switch (test) { // В любом случае ошибка компиляции
    }
    
    // строгие switch + обычные enum class
    TestEnumClass testClass;
    // Успешно скомпилируется
    [[strict]] switch (testClass) {  
        case TestEnumClass::one: cout << "one";
        case TestEnumClass::two: cout << "two";
        default: cout << "not initialized!";
    }
    
    // Ошибка компиляции: нет default ветки для мусора при инициализации
    [[strict]] switch (testClass) {  
        case TestEnumClass::one: cout << "one";
        case TestEnumClass::two: cout << "two";
    }
    
    // Ошибка компиляции: не все случаи enum представлены
    [[strict]] switch (testClass) {  
        case TestEnumClass::one: cout << "one";
        default: cout << "not initialized!";
    }
    
    // Успешно скомпилируется
    [[strict, notfull]] switch (testClass) {  
        case TestEnumClass::one: cout << "one";
        default: cout << "default";
    }
    
    // Строгие switch + строгие enum class
    TestEnumStrict testStrict;
    // Успешно скомпилируется, все случаи enum есть, default ветка отсутствует, как и должна
    [[strict]] switch (testStrict) {  
        case TestEnumStrict::one: cout << "one";
        case TestEnumStrict::two: cout << "two";
    }
    
    // Успешная компиляция, тот случай, когда нужно поработать не со всеми enum-значениями
    [[strict]] switch (testStrict) {  
        case TestEnumStrict::one: cout << "one";
        default: cout << "default";
    }
    
    // Ошибка компиляции: default ветка должна отсутствовать! потому что все enum варианты перебраны
    [[strict]] switch (testStrict) {  
        case TestEnumStrict::one: cout << "one";
        case TestEnumStrict::two: cout << "two";
        default: cout << "default";
    }
    
    // Ошибка компиляции: здесь представлены не все случаи enum!
    [[strict]] switch (testStrict) {  
        case TestEnumStrict::one: cout << "one";
    }
    
    // Ошибка компиляции. не должно быть break инструкции в [[strict]] switch
    [[strict]] switch (testStrict) {  
        case TestEnumStrict::one: cout << "one"; break;
        default: cout << "default";
    }

 

-2
рейтинг
4 комментария
Antervis

В данном случае от аттрибута зависит поведение кода. Однако:
All attributes unknown to an implementation are ignored without causing an error.(since C++17)
Т.е. так сделать не получится. И соседнее предложение тоже не прокатит, по той же причине

Antervis
Обновлено 
Игорь Савенков

Antervis, Может тогда вообще вместо switch какое то другое ключевое слово завести? select? choise?

Игорь Савенков
Игорь Савенков

Игорь Савенков, *choice

Игорь Савенков
Raiden LuiKang

-Wswitch-enum GCC хорошо умеют это.

https://stackoverflow.com/questions/5402745/gcc-switch-on-enum-retain-missing-warning-but-use-default


Visual studio C++ тоже есть такой варнинг. 


https://docs.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-4-c4062?view=vs-2017

Raiden LuiKang
Обновлено 
Другие идеи
Группа создана, чтобы собирать предложения к стандарту C++, организовывать их внутренние обсуждения, помогать готовить их для отправки в комитет и защищать на общих собраниях в рабочей группе по С++ Международной организации по стандартизации (ISO).
Все предложения