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

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

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

Ответ 1

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

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

Оператор if constexpr:

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

Ответ 2

Единственное различие заключается в том, что 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 перегрузки действительны, и код будет скомпилирован успешно.