if constexpr - dlaczego odrzucona instrukcja jest w pełni sprawdzana?

14

W GCC 10 bawiłem się konstelacją c ++ 20 i napisałem ten kod

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

Który ma działać jak algorytm wyszukiwania STL, ale na krotkach i zamiast zwracać iterator, zwraca opcjonalny indeks oparty na predykacie czasu kompilacji. Teraz ten kod dobrze się kompiluje i drukuje

9

Ale jeśli krotka nie zawiera elementu typu integralnego, program się nie kompiluje, ponieważ i.value () jest nadal wywoływana dla pustej opcjonalnej. Dlaczego to jest?

Yamahari
źródło
@AndyG, który tego nie naprawia, prawda? x)
Yamahari,

Odpowiedzi:

11

Właśnie tak działa constexpr, jeśli działa. Jeśli sprawdzimy [stmt.if] / 2

Jeżeli instrukcja if ma postać if constexpr, wartość warunku będzie konwertowanym kontekstowo stałym wyrażeniem typu bool; ta forma nazywa się instrukcją if constexpr. Jeśli wartość przekonwertowanego warunku jest fałszywa, pierwsza podstacja jest odrzuconą instrukcją, w przeciwnym razie druga podstacja, jeśli jest obecna, jest odrzuconą instrukcją. Podczas tworzenia instancji obejmującej encję szablonową ([temp .pre]), jeśli warunek nie jest zależny od wartości po jego instancji, odrzucone podstąpienie (jeśli występuje) nie jest tworzone. [...]

mój nacisk

Widzimy więc, że nie oceniamy odrzuconego wyrażenia tylko wtedy, gdy jesteśmy w szablonie i jeśli warunek zależy od wartości. mainnie jest szablonem funkcji, więc kompilacja instrukcji if jest nadal sprawdzana przez kompilator pod kątem poprawności.

Cppreference mówi również to w swojej sekcji o constexpr, jeśli:

Jeśli instrukcja constexpr if pojawia się wewnątrz encji szablonowej i jeśli warunek nie jest zależny od wartości po utworzeniu instancji, odrzucona instrukcja nie jest tworzona podczas tworzenia instancji zawierającego szablon.

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

Poza szablonem odrzucona instrukcja jest w pełni sprawdzana. jeśli constexpr nie zastępuje dyrektywy #if w zakresie przetwarzania wstępnego:

void f() {
    if constexpr(false) {
        int i = 0;
        int *p = i; // Error even though in discarded statement
    }
}
NathanOliver
źródło
znasz powód tego? wygląda na to, że byłoby to odpowiednie, jeśli constexpr. Więc rozwiązaniem byłoby np. Jakoś zawinięcie go w szablon?
Yamahari,
@Yamahari Ponieważ szablony C ++ mają zarówno mniej, jak i bardziej uporządkowaną strukturę. I tak, zawiń go w szablon (lub napisz jak i.value_or(0))
Barry
2
@Yamahari Tak, rozwiązaniem byłoby umieszczenie kodu w szablonie funkcji. Jeśli chodzi o rozumowanie, nie wiem dlaczego. To byłoby prawdopodobnie dobre pytanie.
NathanOliver,
@Barry value_or (0) działa dobrze, ale w przypadku, gdy krotka ma rozmiar 0
Yamahari
@Yamahari Tak ... niezbyt dobra sugestia z mojej strony.
Barry