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