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?



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

¿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
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.