Разница между "if constexpr()" и "if()"

В чем разница между if constexpr() а также if()?

Где и когда я могу использовать их оба?

2 ответа

Решение

Обычное утверждение if:

  • Оценивается ли его состояние каждый раз, когда контроль достигает его, если когда-либо
  • Определяет, какое из двух подзадач выполнить, пропуская другое
  • Требует, чтобы оба подзаголовка были правильно сформированы независимо от того, какое из них фактически выбрано во время выполнения

Оператор if constexpr:

  • Оценивается ли его условие во время компиляции после предоставления всех необходимых аргументов шаблона
  • Определяет, какое из двух подзаголовков компилировать, отбрасывая другое
  • Не требует, чтобы выброшенная подстанция была правильно сформирована

Единственная разница в том, что if constexpr оценивается во время компиляции, тогда как if не является. Это означает, что ветви могут быть отклонены во время компиляции и, следовательно, никогда не будут скомпилированы.


Представь, что у тебя есть функция, length, который возвращает длину числа или длину типа, который имеет .length() функция. Вы не можете сделать это в одной функции, компилятор будет жаловаться:

template<typename T>
auto length(const T& value) noexcept {
    if (std::integral<T>::value) { // is number
        return value;
    else
        return value.length();
}

int main() noexcept {
    int a = 5;
    std::string b = "foo";

    std::cout << length(a) << ' ' << length(b) << '\n'; // doesn't compile
}

Сообщение об ошибке:

main.cpp: In instantiation of 'auto length(const T&) [with T = int]':
main.cpp:16:26:   required from here
main.cpp:9:16: error: request for member 'length' in 'val', which is of non-class type 'const int'
     return val.length();
            ~~~~^~~~~~

Это потому, что, когда компилятор создает length, функция будет выглядеть так:

auto length(const int& value) noexcept {
    if (std::is_integral<int>::value) { // is number
        return value;
    else
        return value.length();
}

value является intи как таковой не имеет length функция-член, и поэтому компилятор жалуется. Компилятор не может видеть, что утверждение никогда не будет достигнуто для int, но это не имеет значения, так как компилятор не может этого гарантировать.

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

С помощью if constexpr вместо if означает, что ветка (std::is_integral<T>::value) будет оцениваться во время компиляции, и если это true тогда каждая другая ветка (else if а также else) получает отказ. Если это false, следующая ветка проверена (здесь else) и если это true, откажитесь от любой другой ветки и так далее...

template<typename T>
auto length(const T& value) noexcept {
    if constexpr (std::integral<T>::value) { // is number
        return value;
    else
        return value.length();
}

Теперь, когда будет создан экземпляр компилятора length, это будет выглядеть так:

int length(const int& value) noexcept {
    //if (std::is_integral<int>::value) { this branch is taken
        return value;
    //else                           discarded
    //    return value.length();     discarded
}

std::size_t length(const std::string& value) noexcept {
    //if (std::is_integral<int>::value) { discarded
    //    return value;                   discarded
    //else                           this branch is taken
        return value.length();
}

И поэтому эти 2 перегрузки действительны, и код будет успешно скомпилирован.

Другие вопросы по тегам