Diferencia entre "if constexpr()" y "if()"
¿ Cuál es la diferencia entre if constexpr()
y if()
?
¿Dónde y cuándo puedo utilizar ambos?
La única diferencia es que if constexpr
se evalúa en tiempo de compilación, mientras if
que no. Esto significa que las ramas pueden rechazarse en el momento de la compilación y, por lo tanto, nunca se compilarán.
Imagine que tiene una función length
que devuelve la longitud de un número o la longitud de un tipo que tiene una .length()
función. No puedes hacerlo en una función, el compilador se quejará:
template<typename T>
auto length(const T& value) noexcept {
if (std::is_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
}
Mensaje de error:
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();
~~~~^~~~~~
Esto se debe a que cuando el compilador crea una instancia length
, la función se verá así:
auto length(const int& value) noexcept {
if (std::is_integral<int>::value) { // is number
return value;
else
return value.length();
}
value
es un int
, y como tal no tiene una length
función miembro, por lo que el compilador se queja. El compilador no puede ver que nunca se alcanzará la declaración para an int
, pero no importa, ya que el compilador no puede garantizarlo.
Ahora puedes especializarte length
, pero para muchos tipos (como en este caso, cada número y clase con una length
función miembro), esto da como resultado una gran cantidad de código duplicado. SFINAE también es una solución, pero requiere múltiples definiciones de funciones, lo que hace que el código sea mucho más largo de lo necesario para compararlo con el siguiente.
Usar if constexpr
en lugar de if
significa que la rama ( std::is_integral<T>::value
) se evaluará en el momento de la compilación y, si es así, se descartarán true
todas las demás ramas ( else if
y ). else
Si es así false
, se marca la siguiente rama (aquí else
), y si es así true
, descarta todas las demás ramas, y así sucesivamente...
template<typename T>
auto length(const T& value) noexcept {
if constexpr (std::integral<T>::value) { // is number
return value;
else
return value.length();
}
Ahora, cuando el compilador cree una instancia length
, se verá así:
int length(const int& value) noexcept {
//if constexpr (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 constexpr (std::is_integral<int>::value) { discarded
// return value; discarded
//else this branch is taken
return value.length();
}
Entonces esas 2 sobrecargas son válidas y el código se compilará correctamente.
La declaración ordinaria if
:
- ¿Se evalúa su condición cada vez que el control llega hasta él, si es que alguna vez lo logra?
- Determina cuál de las dos subsentencias ejecutar, omitiendo la otra.
- Requiere que ambas subdeclaraciones estén bien formadas independientemente de cuál esté realmente seleccionada en tiempo de ejecución
La if constexpr
declaración:
- ¿Se ha evaluado su condición en el momento de la compilación una vez que se han proporcionado todos los argumentos de plantilla necesarios?
- Determina cuál de las dos subdeclaraciones compilar, descartando la otra.
- No requiere que la subsentencia descartada esté bien formada