Quelle est la différence entre if constexpr()
et if()
?
Où et quand puis-je les utiliser tous les deux?
L'instruction if ordinaire:
L'instruction if constexpr:
La seule différence est que if constexpr
Est évalué au moment de la compilation, tandis que if
ne l'est pas. Cela signifie que les branches peuvent être rejetées au moment de la compilation et ne seront donc jamais compilées.
Imaginez que vous ayez une fonction, length
, qui retourne la longueur d'un nombre ou la longueur d'un type qui a une fonction .length()
. Vous ne pouvez pas le faire dans une seule fonction, le compilateur se plaindra:
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
}
Message d'erreur:
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();
~~~~^~~~~~
C'est parce que lorsque le compilateur instancie length
, la fonction ressemblera à ceci:
auto length(const int& value) noexcept {
if (std::is_integral<int>::value) { // is number
return value;
else
return value.length();
}
value
est un int
, et en tant que tel n'a pas de fonction membre length
, et donc le compilateur se plaint. Le compilateur ne peut pas voir que cette instruction ne sera jamais atteinte pour un int
, mais cela n'a pas d'importance, car le compilateur ne peut pas le garantir.
Maintenant, vous pouvez soit spécialiser length
, mais pour beaucoup de types (comme dans ce cas - chaque numéro et classe avec une fonction membre length
), cela entraîne beaucoup de code dupliqué. SFINAE est également une solution, mais elle nécessite plusieurs définitions de fonctions, ce qui rend le code beaucoup plus long qu'il ne doit être comparé à ce qui suit.
L'utilisation de if constexpr
Au lieu de if
signifie que la branche (std::is_integral<T>::value
) Sera évaluée au moment de la compilation, et si c'est true
, alors toutes les autres branches (else if
Et else
) sont supprimés. Si c'est false
, la branche suivante est vérifiée (ici else
), et si c'est true
, jetez toutes les autres branches, et ainsi de suite ...
template<typename T>
auto length(const T& value) noexcept {
if constexpr (std::integral<T>::value) { // is number
return value;
else
return value.length();
}
Maintenant, lorsque le compilateur instanciera length
, il ressemblera à ceci:
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();
}
Et donc ces 2 surcharges sont valides, et le code se compilera avec succès.