jika constexpr - mengapa pernyataan dibuang sepenuhnya diperiksa?

14

Saya bermain-main dengan c ++ 20 consteval di GCC 10 dan menulis kode ini

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

Yang seharusnya berfungsi seperti algoritma pencarian STL tetapi pada tupel dan bukannya mengembalikan iterator, ia mengembalikan indeks opsional berdasarkan predikat waktu kompilasi. Sekarang kode ini dikompilasi dengan baik dan tercetak

9

Tetapi jika tuple tidak mengandung elemen yang merupakan tipe integral, program tidak mengkompilasi, karena i.value () masih dipanggil pada opsional kosong. Sekarang kenapa begitu?

Yamahari
sumber
@AndyG yang tidak memperbaikinya, bukan? x)
Yamahari

Jawaban:

11

Ini hanya bagaimana constexpr jika berfungsi. Jika kita periksa [stmt.if] / 2

Jika pernyataan if dalam bentuk if constexpr, nilai kondisi harus berupa ekspresi konstanta konstan tipe bool; formulir ini disebut pernyataan constexpr if. Jika nilai kondisi yang dikonversi salah, substatemen pertama adalah pernyataan yang dibuang, sebaliknya subtatement kedua, jika ada, adalah pernyataan yang dibuang. Selama instantiasi entitas templated terlampir ([temp.pre]), jika kondisi tidak bergantung pada nilai setelah instantiasinya, subtasi dibuang (jika ada) tidak dipakai. [...]

penekanan milikku

Jadi kita dapat melihat bahwa kita hanya tidak mengevaluasi ekspresi yang dibuang jika kita berada dalam templat dan jika kondisinya tergantung pada nilai. mainbukan templat fungsi sehingga isi pernyataan if masih diperiksa oleh kompiler untuk kebenaran.

Cppreference juga mengatakan ini di bagian mereka tentang constexpr jika dengan:

Jika pernyataan constexpr if muncul di dalam entitas templated, dan jika kondisi tidak bergantung pada nilai setelah instantiation, pernyataan discarded tidak instantiated ketika templat yang dilampirkan adalah instantiated.

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

Di luar templat, pernyataan yang dibuang sepenuhnya diperiksa. jika constexpr bukan pengganti arahan # jika preprocessing:

void f() {
    if constexpr(false) {
        int i = 0;
        int *p = i; // Error even though in discarded statement
    }
}
NathanOliver
sumber
Anda tahu alasannya? sepertinya ini akan cocok untuk jika constexpr. Juga jadi solusinya misalnya membungkusnya dalam template entah bagaimana?
Yamahari
@Yamahari Karena template C ++ lebih dan kurang terstruktur daripada yang Anda inginkan. Dan ya, bungkus dalam template (atau tulis seperti i.value_or(0))
Barry
2
@Yamahari Ya, solusinya adalah dengan meletakkan kode di templat fungsi. Sejauh nalar, saya tidak tahu mengapa. Itu mungkin pertanyaan yang bagus untuk diajukan.
NathanOliver
@Barry value_or (0) berfungsi dengan baik tetapi untuk kasus ketika tuple adalah ukuran 0
Yamahari
@Yamahari Ya ... bukan saran yang bagus di pihak saya.
Barry