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

14

Je déconnais avec c ++ 20 consteval dans GCC 10 et écrivais 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?

Yamahari
la source
1
Petit
@AndyG qui ne le résout pas, n'est-ce pas? x)
Yamahari

Réponses:

11

C'est ainsi que constexpr fonctionne. 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é de 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. mainn'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é de 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
    }
}
NathanOliver
la source
connaissez-vous le raisonnement pour cela? il semble que ce serait un bon choix pour constexpr. De même, la solution serait par exemple de l'envelopper dans un modèle d'une manière ou d'une autre?
Yamahari
@Yamahari Parce que les modèles C ++ sont à la fois plus et moins structurés que vous ne le souhaiteriez. Et oui, enveloppez-le dans un modèle (ou écrivez comme i.value_or(0))
Barry
2
@Yamahari Oui, la solution serait de mettre le code dans un modèle de fonction. En ce qui concerne le raisonnement, je ne sais pas pourquoi. Ce serait probablement une bonne question à poser.
NathanOliver
@Barry value_or (0) fonctionne bien mais pour le cas où le tuple est de taille 0
Yamahari
@Yamahari Ouais ... pas une bonne suggestion de ma part.
Barry