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