В чем разница между if constexpr()
и if()
?
Где и когда я могу использовать их оба?
В чем разница между if constexpr()
и if()
?
Где и когда я могу использовать их оба?
Обычное утверждение 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 перегрузки действительны, и код будет скомпилирован успешно.