cpp-ru / ideas

Идеи по улучшению языка C++ для обсуждения
https://cpp-ru.github.io/proposals
Creative Commons Zero v1.0 Universal
90 stars 0 forks source link

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

Open apolukhin opened 3 years ago

apolukhin commented 3 years ago

Перенос предложения: голоса +1, -2 Автор идеи: ??

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

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";
    }
apolukhin commented 3 years ago

Antervis, 20 марта 2019, 12:54

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

Игорь Савенков, 20 марта 2019, 13:02 Antervis, Может тогда вообще вместо switch какое то другое ключевое слово завести? select? choise?

Игорь Савенков, 20 марта 2019, 13:05 Игорь Савенков, *choice

Удалённый пользователь, 20 марта 2019, 17:31

-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

pavel-zhigulin commented 3 years ago

Согласен с "удаленный пользователь" :)

Компиляторы давно умеют бросать warning на этот счёт, атрибут не нужен.