web-dev-qa-db-ja.com

「if constexpr()」と「if()」の違い

if constexpr()if()の違いは何ですか?

いつどこで使用できますか?

27
msc

通常のifステートメント:

  • コントロールに到達するたびに、その状態が評価されます
  • 2つのサブステートメントのどちらを実行するかを決定し、もう一方はスキップします
  • 実行時にどちらが実際に選択されているかに関係なく、両方のサブステートメントを整形式にする必要があります。

If constexprステートメント:

  • 必要なすべてのテンプレート引数が提供されると、コンパイル時にその条件が評価されます
  • 2つのサブステートメントのどちらをコンパイルするかを決定し、もう一方を破棄します
  • 破棄されたサブステートメントを整形式にする必要はありません
17
Brian

唯一の違いは、_if constexpr_はコンパイル時に評価されますが、ifは評価されないことです。これは、コンパイル時にブランチが拒否される可能性があるため、コンパイルされないことを意味します。


数値の長さ、または.length()関数を持つ型の長さを返す関数lengthがあるとします。あなたは1つの関数でそれを行うことはできません、コンパイラは文句を言うでしょう:

_template<typename T>
auto length(const T& value) noexcept {
    if (std::integral<T>::value) { // is number
        return value;
    else
        return value.length();
}

int main() noexcept {
    int a = 5;
    std::string b = "foo";

    std::cout << length(a) << ' ' << length(b) << '\n'; // doesn't compile
}
_

エラーメッセージ:

_main.cpp: In instantiation of 'auto length(const T&) [with T = int]':
main.cpp:16:26:   required from here
main.cpp:9:16: error: request for member 'length' in 'val', which is of non-class type 'const int'
     return val.length();
            ~~~~^~~~~~
_

コンパイラがlengthをインスタンス化すると、関数は次のようになるためです。

_auto length(const int& value) noexcept {
    if (std::is_integral<int>::value) { // is number
        return value;
    else
        return value.length();
}
_

valueintであるため、lengthメンバー関数がないため、コンパイラーは不平を言います。コンパイラーは、intについてステートメントに到達しないことを認識できませんが、コンパイラーがそれを保証できないため、問題ではありません。

これでlengthを特殊化できますが、多くの型(この場合-lengthメンバー関数を持つすべての数値とクラス)の場合、これによりコードが重複します。 SFINAEもソリューションですが、複数の関数定義が必要になるため、コードを以下に比べて必要な長さよりもはるかに長くなります。

ifの代わりに_if constexpr_を使用すると、ブランチ(_std::is_integral<T>::value_)がコンパイル時に評価され、trueであれば他のすべてのブランチ(_else if_およびelse)は破棄されます。 falseの場合、次のブランチがチェックされ(ここではelse)、それがtrueの場合、他のすべてのブランチを破棄する、というように続きます...

_template<typename T>
auto length(const T& value) noexcept {
    if constexpr (std::integral<T>::value) { // is number
        return value;
    else
        return value.length();
}
_

これで、コンパイラーがlengthをインスタンス化すると、次のようになります。

_int length(const int& value) noexcept {
    //if (std::is_integral<int>::value) { this branch is taken
        return value;
    //else                           discarded
    //    return value.length();     discarded
}

std::size_t length(const std::string& value) noexcept {
    //if (std::is_integral<int>::value) { discarded
    //    return value;                   discarded
    //else                           this branch is taken
        return value.length();
}
_

そして、これらの2つのオーバーロードは有効であり、コードは正常にコンパイルされます。

20
Rakete1111