if constexpr: ¿por qué se comprueba completamente la declaración descartada?

14

Estaba jugando con c ++ 20 consteval en GCC 10 y escribí este código

#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;
    }
}

Se supone que funciona como el algoritmo de búsqueda STL pero en tuplas y en lugar de devolver un iterador, devuelve un índice opcional basado en un predicado de tiempo de compilación. Ahora este código se compila muy bien y se imprime

9 9

Pero si la tupla no contiene un elemento que es un tipo integral, el programa no se compila, porque i.value () todavía se llama en un opcional vacío. Ahora por qué es eso?

Yamahari
fuente
1
Ejemplo más pequeño
Artyer el
@AndyG que no lo arregla, ¿verdad? x)
Yamahari

Respuestas:

11

Así es como constexpr si funciona. Si verificamos [stmt.if] / 2

Si la instrucción if es de la forma if constexpr, el valor de la condición será una expresión constante convertida contextualmente de tipo bool; este formulario se llama constexpr si la declaración. Si el valor de la condición convertida es falso, la primera subestimación es una declaración descartada, de lo contrario, la segunda subestación, si está presente, es una declaración descartada. Durante la creación de instancias de una entidad con plantilla adjunta ([temp.pre]), si la condición no depende del valor después de su creación de instancias, la subestimación descartada (si existe) no se crea una instancia. [...]

énfasis mío

Entonces podemos ver que solo no evaluamos la expresión descartada si estamos en una plantilla y si la condición depende del valor. mainno es una plantilla de función, por lo que el compilador aún verifica el cuerpo de la instrucción if para verificar que sea correcto

Cppreference también dice esto en su sección sobre constexpr si con:

Si una declaración constexpr if aparece dentro de una entidad con plantilla, y si la condición no depende del valor después de la instanciación, la declaración descartada no se instancia cuando se crea una instancia de la plantilla adjunta.

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.
}

Fuera de una plantilla, una declaración descartada se verifica completamente. si constexpr no es un sustituto de la directiva de preprocesamiento #if:

void f() {
    if constexpr(false) {
        int i = 0;
        int *p = i; // Error even though in discarded statement
    }
}
NathanOliver
fuente
¿Conoces el razonamiento para esto? Parece que esto sería una buena opción si constexpr. Además, ¿la solución sería, por ejemplo, envolverla de alguna manera en una plantilla?
Yamahari
@Yamahari Debido a que las plantillas C ++ están más y menos estructuradas de lo que quieres que estén. Y sí, envuélvala en una plantilla (o escriba como i.value_or(0))
Barry
2
@Yamahari Sí, la solución sería colocar el código en una plantilla de función. En cuanto al razonamiento, no sé por qué. Probablemente sea una buena pregunta para hacer.
NathanOliver
@Barry value_or (0) funciona bien, pero para el caso cuando la tupla es de tamaño 0
Yamahari
@Yamahari Sí ... no es una buena sugerencia de mi parte.
Barry