if constexpr - warum wird die verworfene Anweisung vollständig überprüft?

14

Ich habe mit C ++ 20 Consteval in GCC 10 herumgespielt und diesen Code geschrieben

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

Dies soll wie der STL-Suchalgorithmus funktionieren, jedoch für Tupel. Statt einen Iterator zurückzugeben, wird ein optionaler Index zurückgegeben, der auf einem Prädikat für die Kompilierungszeit basiert. Jetzt wird dieser Code einwandfrei kompiliert und ausgedruckt

9

Wenn das Tupel jedoch kein Element enthält, das ein integraler Typ ist, wird das Programm nicht kompiliert, da i.value () weiterhin für ein leeres optionales Element aufgerufen wird. Warum ist das so?

Yamahari
quelle
@AndyG, das es aber nicht behebt, oder? x)
Yamahari

Antworten:

11

So funktioniert constexpr if . Wenn wir [stmt.if] / 2 überprüfen

Wenn die if-Anweisung die Form if constexpr hat, muss der Wert der Bedingung ein kontextkonvertierter konstanter Ausdruck vom Typ bool sein. Diese Form wird als constexpr if-Anweisung bezeichnet. Wenn der Wert der konvertierten Bedingung falsch ist, ist die erste Unteranweisung eine verworfene Anweisung, andernfalls ist die zweite Unteranweisung, falls vorhanden, eine verworfene Anweisung. Wenn während der Instanziierung einer einschließenden Vorlagenentität ([temp.pre]) die Bedingung nach ihrer Instanziierung nicht wertabhängig ist, wird die verworfene Unteranweisung (falls vorhanden) nicht instanziiert. [...]

Betonung meiner

Wir können also sehen, dass wir den verworfenen Ausdruck nur dann nicht auswerten, wenn wir uns in einer Vorlage befinden und wenn die Bedingung wertabhängig ist. mainist keine Funktionsvorlage, daher wird der Hauptteil der if-Anweisung vom Compiler weiterhin auf Richtigkeit überprüft.

Cppreference sagt dies auch in ihrem Abschnitt über constexpr, wenn mit:

Wenn eine constexpr if-Anweisung in einer Vorlagenentität angezeigt wird und die Bedingung nach der Instanziierung nicht wertabhängig ist, wird die verworfene Anweisung nicht instanziiert, wenn die einschließende Vorlage instanziiert wird.

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

Außerhalb einer Vorlage wird eine verworfene Anweisung vollständig überprüft. Wenn constexpr kein Ersatz für die Vorverarbeitungsrichtlinie #if ist:

void f() {
    if constexpr(false) {
        int i = 0;
        int *p = i; // Error even though in discarded statement
    }
}
NathanOliver
quelle
Kennst du die Gründe dafür? es sieht so aus, als würde dies gut passen, wenn constexpr. Auch so wäre die Lösung zB es irgendwie in eine Vorlage zu wickeln?
Yamahari
@Yamahari Weil C ++ - Vorlagen mehr und weniger strukturiert sind, als Sie möchten. Und ja, wickeln Sie es in eine Vorlage (oder schreiben Sie wie i.value_or(0))
Barry
2
@ Yamahari Ja, die Lösung wäre, den Code in eine Funktionsvorlage einzufügen. Was die Argumentation angeht, weiß ich nicht warum. Das wäre wahrscheinlich eine gute Frage.
NathanOliver
@Barry value_or (0) funktioniert gut, aber für den Fall, dass das Tupel die Größe 0 hat
Yamahari
@ Yamahari Ja ... kein guter Vorschlag von meiner Seite.
Barry