web-dev-qa-db-fra.com

si constexpr - pourquoi l'instruction rejetée est-elle entièrement vérifiée?

Je jouais avec c ++ 20 consteval dans GCC 10 et j'ai écrit ce code

#include <optional>
#include <Tuple>
#include <iostream>

template <std::size_t N, typename Predicate, typename Tuple>
consteval std::optional<std::size_t> find_if_impl(Predicate&& pred,
                                                  Tuple&& t) noexcept {
  constexpr std::size_t I = std::Tuple_size_v<std::decay_t<decltype(t)>> - N;

  if constexpr (N == 0u) {
    return std::nullopt;
  } else {
    return pred(std::get<I>(t))
               ? std::make_optional(I)
               : find_if_impl<N - 1u>(std::forward<decltype(pred)>(pred),
                                      std::forward<decltype(t)>(t));
  }
}

template <typename Predicate, typename Tuple>
consteval std::optional<std::size_t> find_if(Predicate&& pred,
                                             Tuple&& t) noexcept {
  return find_if_impl<std::Tuple_size_v<std::decay_t<decltype(t)>>>(
      std::forward<decltype(pred)>(pred), std::forward<decltype(t)>(t));
}

constexpr auto is_integral = [](auto&& x) noexcept {
    return std::is_integral_v<std::decay_t<decltype(x)>>;
};


int main() {
    auto t0 = std::make_Tuple(9, 1.f, 2.f);
    constexpr auto i = find_if(is_integral, t0);
    if constexpr(i.has_value()) {
        std::cout << std::get<i.value()>(t0) << std::endl;
    }
}

Qui est censé fonctionner comme l'algorithme de recherche STL mais sur des tuples et au lieu de renvoyer un itérateur, il retourne un index facultatif basé sur un prédicat de temps de compilation. Maintenant, ce code se compile très bien et il s'imprime

9

Mais si le Tuple ne contient pas d'élément de type intégral, le programme ne compile pas, car i.value () est toujours appelé sur une option vide. Maintenant, pourquoi ça?

14
Yamahari

Voici comment fonctionne constexpr if . Si nous vérifions [stmt.if]/2

Si l'instruction if est de la forme if constexpr, la valeur de la condition doit être une expression constante de type bool convertie contextuellement; ce formulaire est appelé une instruction if constexpr. Si la valeur de la condition convertie est fausse, la première sous-instruction est une instruction rejetée, sinon la deuxième sous-instruction, si elle est présente, est une instruction ignorée. Pendant l'instanciation d'une entité modèle englobante ([temp.pre]), si la condition ne dépend pas de la valeur après son instanciation, la sous-instruction rejetée (le cas échéant) n'est pas instanciée. [...]

mettre l'accent

Nous pouvons donc voir que nous n'évaluons l'expression que si nous sommes dans un modèle et si la condition dépend de la valeur. main n'est pas un modèle de fonction, donc le corps de l'instruction if est toujours vérifié par le compilateur pour l'exactitude.

Cppreference le dit également dans leur section sur constexpr si avec:

Si une instruction constexpr if apparaît à l'intérieur d'une entité basée sur un modèle et si la condition ne dépend pas de la valeur après l'instanciation, l'instruction rejetée n'est pas instanciée lorsque le modèle englobant est instancié.

template<typename T, typename ... Rest>
void g(T&& p, Rest&& ...rs) {
    // ... handle p
    if constexpr (sizeof...(rs) > 0)
        g(rs...); // never instantiated with an empty argument list.
}

En dehors d'un modèle, une instruction rejetée est entièrement vérifiée. si constexpr n'est pas un substitut à la directive de prétraitement #if:

void f() {
    if constexpr(false) {
        int i = 0;
        int *p = i; // Error even though in discarded statement
    }
}
11
NathanOliver