if constexpr-破棄されたステートメントが完全にチェックされるのはなぜですか?


14

私はGCC 10でc ++ 20 constevalをいじくり回して、このコードを書きました

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

これはSTL検索アルゴリズムのように機能するはずですが、タプルではイテレータを返す代わりに、コンパイル時の述語に基づいてオプションのインデックスを返します。今、このコードはうまくコンパイルされ、出力されます

9

しかし、タプルに整数型の要素が含まれていない場合、プログラムはコンパイルされません。i.value()が空のオプションで呼び出されるためです。なぜですか?



それを修正しない@AndyG、それはそうですか?x)
Yamahari

回答:


11

これは、constexprが機能する場合の方法です[stmt.if] / 2をチェックすると

ifステートメントがif constexprの形式である場合、条件の値はbool型のコンテキスト変換された定数式でなければなりません。この形式はconstexpr ifステートメントと呼ばれます。変換された条件の値がfalseの場合、最初のサブステートメントは破棄されたステートメントです。それ以外の場合、2番目のサブステートメント(存在する場合)は破棄されたステートメントです。囲んでいるテンプレート化されたエンティティ([temp.pre])のインスタンス化中に、インスタンス化後に条件が値に依存しない場合、破棄されたサブステートメント(存在する場合)はインスタンス化されません。[...]

重点鉱山

したがって、テンプレート内にあり、条件が値に依存している場合にのみ、破棄された式を評価しないことがわかります。 mainは関数テンプレートではないため、ifステートメントの本文はコンパイラーによって正しいかどうかが引き続きチェックされます。

Cppreferenceはまた、次の場合のconstexprに関するセクションでこれを述べています。

constexpr ifステートメントがテンプレート化されたエンティティ内に出現し、インスタンス化の後に条件が値に依存しない場合、外側のテンプレートがインスタンス化されても、破棄されたステートメントはインスタンス化されません。

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

テンプレートの外では、破棄されたステートメントが完全にチェックされます。constexprが#if前処理ディレクティブの代わりにならない場合:

void f() {
    if constexpr(false) {
        int i = 0;
        int *p = i; // Error even though in discarded statement
    }
}

これの理由を知っていますか?これはconstexprの場合に適しているようです。また、解決策は、たとえばそれをテンプレートに何らかの形でラップすることでしょうか?
ヤマハリ

@Yamahari C ++テンプレートは、思ったよりも構造化されているためです。そして、はい、それをテンプレートでラップします(またはのように書きますi.value_or(0)
Barry

2
@Yamahariはい、解決策はコードを関数テンプレートに配置することです。推論に関する限り、私はその理由を知りません。それはおそらく質問するのに良い質問でしょう。
NathanOliver

@Barry value_or(0)は問題なく機能しますが、タプルのサイズが0の場合
Yamahari

@Yamahariそうだね...私の側では良い提案ではない。
バリー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.