if constexpr - por que a declaração descartada é totalmente verificada?

14

Eu estava brincando com c ++ 20 consteval no GCC 10 e escrevi 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;
    }
}

Que deve funcionar como o algoritmo de localização STL, mas em tuplas e, em vez de retornar um iterador, ele retorna um índice opcional com base em um predicado de tempo de compilação. Agora esse código compila muito bem e é impresso

9

Mas se a tupla não contiver um elemento de tipo integral, o programa não será compilado, porque o i.value () ainda é chamado em um opcional vazio. Agora, por que isso?

Yamahari
fonte
11
Exemplo menor
Artyer 18/12/19
@ AndyG que não corrige, porém, corrige? x)
Yamahari

Respostas:

11

É assim que o constexpr funciona. Se verificarmos [stmt.if] / 2

Se a instrução if for da forma if constexpr, o valor da condição deve ser uma expressão constante convertida contextualmente do tipo bool; esse formulário é chamado de declaração constexpr if. Se o valor da condição convertida for falso, a primeira subestação é uma instrução descartada, caso contrário, a segunda subestação, se presente, é uma instrução descartada. Durante a instanciação de uma entidade de modelo envolvente ([temp.pre]), se a condição não depender de valor após a instanciação, a subestação descartada (se houver) não será instanciada. [...]

ênfase minha

Portanto, podemos ver que apenas não avaliamos a expressão descartada se estivermos em um modelo e se a condição for dependente de valor. mainnão é um modelo de função, portanto, o corpo da instrução if ainda é verificado pelo compilador quanto à correção.

O Cppreference também diz isso em sua seção sobre constexpr se com:

Se uma instrução constexpr if aparecer dentro de uma entidade de modelo, e se a condição não for dependente de valor após a instanciação, a instrução descartada não será instanciada quando o modelo anexo for instanciado.

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

Fora de um modelo, uma instrução descartada é totalmente verificada. se constexpr não for um substituto para a diretiva de pré-processamento #if:

void f() {
    if constexpr(false) {
        int i = 0;
        int *p = i; // Error even though in discarded statement
    }
}
NathanOliver
fonte
você sabe o motivo disso? parece que isso seria um bom ajuste para se constexpr. Além disso, a solução seria, por exemplo, envolvê-la em um modelo de alguma forma?
Yamahari
@Yamahari Porque os modelos C ++ são mais e menos estruturados do que você deseja. E sim, envolvê-la em um modelo (ou escrever como i.value_or(0))
Barry
2
@Yamahari Sim, a solução seria colocar o código em um modelo de função. Quanto ao raciocínio, não sei por quê. Provavelmente seria uma boa pergunta a ser feita.
NathanOliver
@ Barry value_or (0) funciona bem, mas para o caso em que a tupla é do tamanho 0
Yamahari 18/12/19
@Yamahari Sim ... não é uma boa sugestão da minha parte.
Barry