QtプログラミングのTIPS:QMap::removeIf()でコードを簡潔に

2025-05-31

これは Qt の QMap クラスのメンバ関数で、特定の条件を満たす要素をマップから削除するために使用されます。C++ のテンプレートと関数オブジェクト(ファンクタ)またはラムダ式を活用することで、非常に柔軟な削除条件を指定できます。

各部分の解説

    • これはC++のテンプレート構文です。Predicate はプレースホルダー(型パラメータ)であり、removeIf 関数を呼び出す際に具体的な型に置き換えられます。
    • この Predicate には、関数呼び出し演算子 () をオーバーロードしたクラス(関数オブジェクト、ファンクタ)や、ラムダ式など、ブール値を返す呼び出し可能なものが来ることが期待されます。
  1. QMap<Key, T>::size_type

    • これは removeIf 関数の戻り値の型です。
    • QMap<Key, T>::size_type は、QMap に含まれる要素の数を表すために使用される符号なし整数型です。具体的には、この関数が削除した要素の数を返します。
  2. QMap::removeIf(Predicate predicate)

    • これは QMap クラスのメンバ関数です。
    • 引数 predicatePredicate 型のオブジェクトです。この predicate は、マップ内の各要素に対して適用される「条件」を定義します。

動作原理

removeIf() メソッドが呼び出されると、QMap は内部的にすべてのキーと値のペアを反復処理します。各ペアについて、引数として渡された predicate を呼び出します。

  • predicatefalse を返した場合、そのペアはマップに保持されます。
  • predicatetrue を返した場合、そのキーと値のペアは QMap から削除されます。

最終的に、削除された要素の総数が戻り値として返されます。

Predicate の例

Predicate としては、以下のようなものが考えられます。

  1. ラムダ式 (Lambda Expression): 最も一般的で簡潔な方法です。

    QMap<QString, int> myMap;
    myMap["apple"] = 10;
    myMap["banana"] = 5;
    myMap["cherry"] = 12;
    
    // 値が10より小さい要素を削除
    int removedCount = myMap.removeIf([](const QString& key, int value) {
        return value < 10;
    });
    // removedCount は 1 (banana)
    // myMap は {"apple": 10, "cherry": 12}
    
  2. 関数オブジェクト (Function Object / Functor): operator() をオーバーロードしたクラスのインスタンス。複雑なロジックや状態を持つ条件を定義する場合に便利です。

    class ValueGreaterThanPredicate {
    public:
        ValueGreaterThanPredicate(int threshold) : m_threshold(threshold) {}
    
        bool operator()(const QString& key, int value) const {
            Q_UNUSED(key); // キーを使用しない場合はQ_UNUSEDで警告を抑制
            return value > m_threshold;
        }
    
    private:
        int m_threshold;
    };
    
    QMap<QString, int> myMap;
    myMap["apple"] = 10;
    myMap["banana"] = 5;
    myMap["cherry"] = 12;
    
    // 値が10より大きい要素を削除
    int removedCount = myMap.removeIf(ValueGreaterThanPredicate(10));
    // removedCount は 1 (cherry)
    // myMap は {"apple": 10, "banana": 5}
    
  3. グローバル関数 / 静的メンバ関数: まれですが、これらの関数ポインタも Predicate として使用できます。

    bool isEvenValue(const QString& key, int value) {
        Q_UNUSED(key);
        return value % 2 == 0;
    }
    
    QMap<QString, int> myMap;
    myMap["one"] = 1;
    myMap["two"] = 2;
    myMap["three"] = 3;
    myMap["four"] = 4;
    
    // 値が偶数の要素を削除
    int removedCount = myMap.removeIf(isEvenValue);
    // removedCount は 2 (two, four)
    // myMap は {"one": 1, "three": 3}
    

利点

  • 効率性: 内部で効率的な方法で要素を反復処理し、削除します。手動でイテレータを操作して削除するよりも安全で間違いが少ないです。
  • 簡潔性: 特にラムダ式を使用する場合、削除ロジックをインラインで記述でき、コードが読みやすくなります。
  • 柔軟性: 任意の条件に基づいて要素を削除できます。

注意点

  • predicateconst QMap<Key, T>::key_type& keyconst QMap<Key, T>::mapped_type& value を引数として受け取る必要があります(またはそれらに変換可能な型)。キーのみ、または値のみをチェックしたい場合は、使用しない引数に Q_UNUSED を適用して警告を抑制できます。
  • removeIf() はオリジナルの QMap を直接変更します。


Predicateの引数型が間違っている

最もよくある間違いの一つです。removeIf() に渡す Predicate(ラムダ式や関数オブジェクト)は、以下のいずれかのシグネチャを持つ必要があります。

  • bool operator()(QMap<Key, T>::iterator it) const (Qt 6.1 以降)
  • bool operator()(const std::pair<const Key&, T&> pair) const (Qt 6.1 以降)
  • bool operator()(const QMap<Key, T>::key_type& key, const QMap<Key, T>::mapped_type& value) const

一般的なエラー

  • const 修飾が抜けている
    関数オブジェクトの場合、operator()const が付いていないと、removeIfconst バージョンを呼び出そうとしたときにコンパイルエラーになることがあります。ラムダ式の場合は、デフォルトで const なのであまり問題になりません。
  • 引数の型が違う
    (Key key, T value) のように、参照や const が抜けている。特に Key が複雑な型の場合、コピーオーバーヘッドも問題になることがあります。
  • 引数の数が違う
    (int value) のように、キーを受け取らず値だけを受け取ろうとする。

トラブルシューティング

  • ラムダ式の型を正確に指定する
    QMap<QString, int> myMap;
    // ...
    myMap.removeIf([](const QString& key, int value) { // key と value は const 参照と値型
        return value < 10;
    });
    
    または Qt 6.1 以降で std::pair を使う場合:
    myMap.removeIf([](const std::pair<const QString&, int&> pair) {
        return pair.second < 10; // pair.first はキー、pair.second は値
    });
    
    QMap<Key, T>::iterator を使う場合(特にキーや値の変更が必要な場合):
    myMap.removeIf([](QMap<QString, int>::iterator it) {
        // it.key() や it.value() を使用
        return it.value() < 10;
    });
    
  • 公式ドキュメントを参照する
    QMap::removeIf() のドキュメントには、Predicate の期待されるシグネチャが明記されています。
  • エラーメッセージを確認する
    コンパイラのエラーメッセージは、多くの場合、期待される引数型を示唆しています。no matching function for call to 'QMap<...>::removeIf(lambda)' のようなエラーが出た場合、removeIf が探している Predicate のシグネチャと、実際に渡しているラムダや関数オブジェクトのシグネチャが一致しているか確認します。

ラムダ式のキャプチャの問題

ラムダ式を使用する場合、外部変数をキャプチャする方法によって問題が発生することがあります。

一般的なエラー

  • 無効なキャプチャ
    int threshold = 10;
    // QMap<QString, int> myMap;
    // ...
    myMap.removeIf([&](const QString& key, int value) {
        // threshold を参照キャプチャしているが、removeIfが非同期に実行される可能性は低い。
        // しかし、QObjectのシグナル/スロットなどでラムダを渡す場合は、
        // ライフタイムに注意が必要になる。
        return value < threshold;
    });
    
    これは removeIf の直接的な呼び出しでは問題になりにくいですが、ラムダを他のスレッドや後で実行されるスロットに渡す場合に、参照している変数がすでに破棄されている(ダングリング参照)といった問題が発生する可能性があります。

トラブルシューティング

  • スマートポインタでキャプチャする
    ヒープ上のオブジェクトをキャプチャする場合、std::shared_ptr などを使用してライフタイムを管理します。
  • 値キャプチャ [=] または明示的な値キャプチャ [variable_name] を使用する
    これにより、ラムダがコピーされ、外部変数のライフタイムに依存しなくなります。
    int threshold = 10;
    myMap.removeIf([threshold](const QString& key, int value) { // threshold を値でキャプチャ
        return value < threshold;
    });
    

Predicate内部でのマップの変更

removeIf はマップを反復処理しながら要素を削除します。Predicate の中で直接 QMap を変更(追加や削除)しようとすると、イテレータが無効になり、未定義の動作やクラッシュを引き起こす可能性があります。

一般的なエラー

QMap<int, QString> myMap = {{1, "one"}, {2, "two"}, {3, "three"}};
myMap.removeIf([&](int key, const QString& value) {
    if (value == "two") {
        myMap.insert(4, "four"); // NG: predicate内でマップを変更
        return true;
    }
    return false;
});

トラブルシューティング

  • もし要素の削除以外の操作(追加など)が必要な場合は、removeIf() の呼び出しとは別に、処理を行う必要があります。例えば、削除対象のキーをリストに格納し、removeIf() の後にそのリストを使って追加や他の操作を行うなどです。
  • removeIf()predicate は、マップの現在の要素の検査のみを行うべきです。

Predicate の意図しない副作用

predicate 関数は、あくまで要素を削除するかどうかを決定するためのブール値を返す役割に徹するべきです。predicate 内部で、例えばログ出力以外の、マップの外部の状態を変更するような副作用を持つ処理を行うと、予期せぬ結果を招く可能性があります。removeIf が内部でどのように反復処理や要素の評価を行うかについての保証はないため、副作用の発生順序などが不定になる可能性があります。

トラブルシューティング

  • 削除される要素に対して何らかの処理を行いたい場合は、removeIf の前に個別に処理するか、別の反復処理パスを使用することを検討します。
  • predicate を可能な限り「純粋な関数」に保ち、副作用を持たせないようにします。

ポインタを含む QMap でのメモリリーク

QMap<Key, SomePointerType*> のようにポインタを値として格納している場合、removeIf はマップからポインタを削除しますが、ポインタが指す先のメモリを解放しません。これによりメモリリークが発生する可能性があります。

一般的なエラー

QMap<int, MyObject*> objectMap;
objectMap.insert(1, new MyObject());
objectMap.insert(2, new MyObject());

// 値が特定の条件を満たす要素を削除
// これではMyObjectインスタンスは解放されない!
objectMap.removeIf([](int key, MyObject* obj) {
    return obj->someCondition();
});
// メモリリーク!
  • 削除前に手動でメモリを解放する
    removeIf の呼び出しの前に、削除対象となる要素を特定し、手動で delete するループを回します。
    QList<int> keysToRemove;
    for (auto it = objectMap.constBegin(); it != objectMap.constEnd(); ++it) {
        if (it.value()->someCondition()) {
            delete it.value(); // ポインタが指すオブジェクトを解放
            keysToRemove.append(it.key());
        }
    }
    for (int key : keysToRemove) {
        objectMap.remove(key); // QMapからキーを削除
    }
    // または、Qt 6.1 以降で Predicate にイテレータを渡せる場合:
    // myMap.removeIf([](QMap<int, MyObject*>::iterator it) {
    //     if (it.value()->someCondition()) {
    //         delete it.value(); // ここで解放
    //         return true;
    //     }
    //     return false;
    // });
    
    この手動での削除方法は、removeIf の内部処理と競合しないように注意が必要です。removeIf の Predicate で直接 delete する方法もありますが、これは removeIf の実装が it.value() の評価と削除が同一イテレーション内で行われることを前提とするため、若干のリスクが伴います。通常は、std::erase_if のように、Predicate が std::pair<const Key&, T&> を受け取るタイプであれば安全に値にアクセスして delete できます。
  • QMap<Key, QSharedPointer<MyObject>> のようにスマートポインタを使用する
    これが最も推奨される方法です。スマートポインタはオブジェクトのライフタイムを自動的に管理します。


QMap::removeIf() は、指定した条件に基づいてマップから要素を削除するための非常に便利な関数です。主にラムダ式と組み合わせて使用されます。

基本的な使用例:値に基づいて削除する

最も一般的なケースで、特定の条件を満たす「値」を持つ要素を削除します。

#include <QCoreApplication>
#include <QMap>
#include <QDebug> // デバッグ出力用

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QMap<QString, int> studentScores;
    studentScores.insert("Alice", 85);
    studentScores.insert("Bob", 60);
    studentScores.insert("Charlie", 92);
    studentScores.insert("David", 45);
    studentScores.insert("Eve", 70);

    qDebug() << "Original map:" << studentScores;
    // 出力例: Original map: QMap(("Alice", 85)("Bob", 60)("Charlie", 92)("David", 45)("Eve", 70))

    // スコアが60未満の学生を削除する
    // ラムダ式の引数は (キー, 値) の const 参照である必要があります。
    int removedCount = studentScores.removeIf([](const QString& name, int score) {
        Q_UNUSED(name); // name はここでは使わないので Q_UNUSED で警告を抑制
        return score < 60;
    });

    qDebug() << "Removed" << removedCount << "students.";
    // 出力例: Removed 1 students. (David が削除される)

    qDebug() << "Map after removal:" << studentScores;
    // 出力例: Map after removal: QMap(("Alice", 85)("Bob", 60)("Charlie", 92)("Eve", 70))

    return 0;
}

解説

  • この例では、"David" (スコア 45) が削除され、removedCount1 になります。
  • ラムダ式は、score60 未満の場合に true を返します。true が返された要素はマップから削除されます。
  • ラムダ式 [](const QString& name, int score) { ... }Predicate として渡されます。
    • const QString& name: マップのキーの型 (QString) の const 参照。
    • int score: マップの値の型 (int)。Qt 5.14 以降では、int& score (非 const 参照) も許容されますが、通常は値渡しまたは const 参照で十分です。
  • studentScores.removeIf(...) が呼び出されます。

キーに基づいて削除する

キーの条件に基づいて要素を削除する例です。

#include <QCoreApplication>
#include <QMap>
#include <QDebug>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QMap<int, QString> productNames;
    productNames.insert(101, "Laptop");
    productNames.insert(205, "Mouse");
    productNames.insert(102, "Monitor");
    productNames.insert(300, "Keyboard");
    productNames.insert(103, "Webcam");

    qDebug() << "Original map:" << productNames;

    // 商品IDが100番台の製品を削除する
    int removedCount = productNames.removeIf([](int id, const QString& name) {
        Q_UNUSED(name); // name はここでは使わない
        return id >= 100 && id < 200; // 100以上200未満
    });

    qDebug() << "Removed" << removedCount << "products.";
    // 出力例: Removed 3 products. (101:Laptop, 102:Monitor, 103:Webcam が削除される)

    qDebug() << "Map after removal:" << productNames;
    // 出力例: Map after removal: QMap((205, "Mouse")(300, "Keyboard"))

    return 0;
}

解説

  • この条件に合致するキー (101, 102, 103) の要素が削除されます。
  • ラムダ式内で、id100 以上 200 未満であるか(つまり100番台か)をチェックしています。

キーと値の両方に基づいて削除する

より複雑な条件として、キーと値の両方を見て削除するかどうかを決定する例です。

#include <QCoreApplication>
#include <QMap>
#include <QDebug>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QMap<QString, QString> userRoles;
    userRoles.insert("admin_john", "Administrator");
    userRoles.insert("user_mary", "Editor");
    userRoles.insert("admin_jane", "Administrator");
    userRoles.insert("guest_mike", "Viewer");
    userRoles.insert("super_admin", "Administrator");

    qDebug() << "Original map:" << userRoles;

    // "admin_" で始まり、かつロールが "Administrator" ではないユーザーを削除する
    int removedCount = userRoles.removeIf([](const QString& username, const QString& role) {
        return username.startsWith("admin_") && role != "Administrator";
    });

    qDebug() << "Removed" << removedCount << "users.";
    // 出力例: Removed 0 users. (このデータでは該当するユーザーがいないため)

    // 例を変えて、"user_" で始まるユーザーを削除する
    // (ユーザーのロールに関わらず)
    removedCount = userRoles.removeIf([](const QString& username, const QString& role) {
        Q_UNUSED(role);
        return username.startsWith("user_");
    });

    qDebug() << "Removed (user_) " << removedCount << "users.";
    // 出力例: Removed (user_)  1 users. (user_mary が削除される)

    qDebug() << "Map after second removal:" << userRoles;
    // 出力例: Map after second removal: QMap(("admin_jane", "Administrator")("admin_john", "Administrator")("guest_mike", "Viewer")("super_admin", "Administrator"))


    return 0;
}

解説

  • 二番目の removeIf では、キーが "user_" で始まるユーザーを削除しています。この場合、"user_mary" が削除されます。
  • 最初の removeIf では、キーが "admin_" で始まり、かつ値が "Administrator" ではないという複合条件をチェックしています。この初期データセットでは、このようなユーザーは存在しないため、何も削除されません。

関数オブジェクト (Functor) をPredicateとして使用する

ラムダ式が使えない環境(古いC++標準)や、Predicateがより複雑な状態を持つ必要がある場合に、関数オブジェクトを使用できます。

#include <QCoreApplication>
#include <QMap>
#include <QDebug>

// 特定の閾値より小さい値を削除するための関数オブジェクト
class ValueLessThanPredicate
{
public:
    ValueLessThanPredicate(int threshold) : m_threshold(threshold) {}

    // QMap::removeIf が期待するシグネチャに合わせる
    bool operator()(const QString& key, int value) const {
        Q_UNUSED(key);
        return value < m_threshold;
    }

private:
    int m_threshold;
};

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QMap<QString, int> productQuantities;
    productQuantities.insert("Laptop", 5);
    productQuantities.insert("Mouse", 20);
    productQuantities.insert("Monitor", 3);
    productQuantities.insert("Keyboard", 15);

    qDebug() << "Original map:" << productQuantities;

    // 在庫数が10未満の製品を削除する
    ValueLessThanPredicate predicate(10); // 閾値10でPredicateオブジェクトを作成
    int removedCount = productQuantities.removeIf(predicate);

    qDebug() << "Removed" << removedCount << "products with quantity less than 10.";
    // 出力例: Removed 2 products with quantity less than 10. (Laptop, Monitor が削除される)

    qDebug() << "Map after removal:" << productQuantities;
    // 出力例: Map after removal: QMap(("Keyboard", 15)("Mouse", 20))

    return 0;
}
  • ラムダ式と同様に、m_threshold を使って条件を評価し、true または false を返します。
  • operator()(const QString& key, int value) const がオーバーロードされており、これが QMap::removeIf() によって各要素に適用されます。
  • ValueLessThanPredicate クラスは、コンストラクタで閾値を受け取り、それをメンバ変数 m_threshold に保持します。


主に以下の3つの方法が挙げられます。

  1. イテレータを使用した手動ループと要素削除 (C++11以前の典型的な方法)
  2. QMap::filter() + QMap::clear() + QMap::unite() (Qt 6.0以降)
  3. std::erase_if の利用 (Qt 6.2以降のC++標準ライブラリの機能)

イテレータを使用した手動ループと要素削除

これは最も一般的で、Qtのどのバージョンでも動作する伝統的な方法です。マップをイテレートし、条件に合致する要素を削除します。要素を削除するとイテレータが無効になるため、注意深くコードを書く必要があります。

#include <QCoreApplication>
#include <QMap>
#include <QDebug>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QMap<QString, int> studentScores;
    studentScores.insert("Alice", 85);
    studentScores.insert("Bob", 60);
    studentScores.insert("Charlie", 92);
    studentScores.insert("David", 45);
    studentScores.insert("Eve", 70);

    qDebug() << "Original map:" << studentScores;

    // スコアが60未満の学生を削除する (手動ループ)
    int removedCount = 0;
    auto it = studentScores.begin(); // const_iterator ではなく begin() を使う
    while (it != studentScores.end()) {
        if (it.value() < 60) { // 削除条件
            it = studentScores.erase(it); // erase() は次の有効なイテレータを返す
            removedCount++;
        } else {
            ++it; // 条件に合致しない場合は次の要素へ進む
        }
    }

    qDebug() << "Removed" << removedCount << "students (manual loop).";
    qDebug() << "Map after removal:" << studentScores;

    return 0;
}

解説

  • 条件に合致しなかった場合は、++it で明示的に次の要素に進む必要があります。
  • 条件に合致して削除した場合は、it が既に次の要素を指しているので ++it は不要です。
  • studentScores.erase(it)
    これが重要なポイントです。要素を削除すると、その要素を指していたイテレータは無効になります。QMap::erase() (Qt 4以降) は、削除された要素の次の要素を指す有効なイテレータを返します。この戻り値を使って it を更新することで、ループを安全に続行できます。
  • it.value() で現在の要素の値にアクセスし、削除条件をチェックします。
  • QMap::begin()QMap::end() を使ってマップを反復処理します。

利点

  • 柔軟性が高い。
  • どのQtバージョンでも動作する。

欠点

  • バグを導入しやすい(特にイテレータの無効化に対する理解が不十分な場合)。
  • erase() の戻り値を使ってイテレータを更新する必要があり、コードがやや複雑になる。

QMap::filter() + QMap::clear() + QMap::unite() (Qt 6.0以降)

Qt 6.0以降では、QMap::filter() という便利な関数が追加されました。これは、条件を満たす要素を持つ新しいマップを生成します。これを利用して、削除したい要素ではない「残したい要素」をフィルタリングし、元のマップを置き換えることで、removeIf() と同じ結果を得られます。

#include <QCoreApplication>
#include <QMap>
#include <QDebug>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QMap<QString, int> studentScores;
    studentScores.insert("Alice", 85);
    studentScores.insert("Bob", 60);
    studentScores.insert("Charlie", 92);
    studentScores.insert("David", 45);
    studentScores.insert("Eve", 70);

    qDebug() << "Original map:" << studentScores;

    // スコアが60未満の学生を削除する (filter() を利用)
    // ここでは「残したい」要素の条件を指定する
    QMap<QString, int> remainingStudents = studentScores.filter([](const QString& name, int score) {
        Q_UNUSED(name);
        return score >= 60; // スコアが60以上の学生は残す
    });

    // removedCount は手動で計算するか、元のマップのサイズと比較する
    int removedCount = studentScores.size() - remainingStudents.size();

    // 元のマップをクリアし、フィルタリングされた要素で置き換える
    studentScores.clear();
    studentScores.unite(remainingStudents); // unite() はキーが重複しない場合に追加する

    qDebug() << "Removed" << removedCount << "students (filter() method).";
    qDebug() << "Map after removal:" << studentScores;

    return 0;
}

解説

  • unite() は、引数のマップのキーが現在のマップに存在しない場合にのみ要素を追加します。既存の要素を上書きするわけではありません。今回はクリアしてからなので問題ありません。
  • filter() は新しい QMap を返すため、元のマップ studentScores をクリアし、フィルタリングされた remainingStudents の内容を unite() で結合し直します。
  • studentScores.filter(...) を使って、削除したいものではなく、残したい要素(スコアが60以上)をフィルタリングします。

利点

  • ラムダ式で条件を簡潔に記述できる。
  • イテレータの無効化を心配する必要がない。

欠点

  • Qt 6.0以降でしか利用できない。
  • 新しい QMap を作成するため、メモリとパフォーマンスのオーバーヘッドがある場合がある(特にマップが大きい場合)。

std::erase_if の利用 (C++20以降)

C++20では、標準ライブラリに std::erase_if という非常に便利な関数が追加されました。これは、コンテナ(std::mapstd::vector など)から条件を満たす要素を効率的に削除するためのものです。Qtのコンテナは多くの場合、std::map と同様に std::erase_if を適用できます。

注意
QMap は厳密には std::map ではありませんが、Qt 6.2以降で std::erase_if を使用できるオーバーロードが追加された可能性があります。しかし、一般的には QMap の要素は std::pair のように振る舞うため、以下のようにキーと値をペアとして受け取るラムダを記述することで機能します。

#include <QCoreApplication>
#include <QMap>
#include <QDebug>
#include <algorithm> // std::erase_if を含む

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QMap<QString, int> studentScores;
    studentScores.insert("Alice", 85);
    studentScores.insert("Bob", 60);
    studentScores.insert("Charlie", 92);
    studentScores.insert("David", 45);
    studentScores.insert("Eve", 70);

    qDebug() << "Original map:" << studentScores;

    // スコアが60未満の学生を削除する (std::erase_if を利用)
    // QMap は std::erase_if に対応するための適切な overloads を提供しているか、
    // あるいはQMapが提供するイテレータと erase_if の動作が互換である必要がある
    // Qt 6.2以降でQMapに対するstd::erase_ifのサポートが明示的に追加されています
    auto removedCount = std::erase_if(studentScores, [](const auto& pair) {
        return pair.second < 60; // pair.first はキー、pair.second は値
    });

    qDebug() << "Removed" << removedCount << "students (std::erase_if).";
    qDebug() << "Map after removal:" << studentScores;

    return 0;
}

解説

  • std::erase_if は、削除された要素の数を返します。
  • Predicate は、コンテナの要素型(QMap の場合は QPair<Key, T> のようなものとして扱われるか、イテレータが指す要素が std::pair<const Key, T> として扱われるか)を受け取ります。ここでは const auto& pair として受け取り、pair.second (値) をチェックしています。
  • std::erase_if は第一引数にコンテナ、第二引数に Predicate(ラムダ式など)を取ります。

利点

  • C++標準ライブラリの一部。
  • イテレータの無効化を心配する必要がない(std::erase_if が内部で安全に処理するため)。
  • 非常に簡潔で読みやすい。
  • Qtの特定のバージョン(Qt 6.2以降)で QMapstd::erase_if に対応している必要がある。
  • C++20以降のコンパイラと標準ライブラリが必要。
  • C++20とQt 6.2以降であれば、std::erase_if も強力な選択肢です。 標準ライブラリの統一されたインターフェースを利用できます。
  • Qt 6.0以降であれば、QMap::filter() を使った方法も選択肢になります。 ただし、新しいマップ生成のオーバーヘッドを考慮する必要があります。
  • Qt 5.14以降であれば、QMap::removeIf() が最も推奨されます。 コードの簡潔さと安全性が両立しています。
  • 最も柔軟で古いQtバージョンでも動くのは「イテレータを使用した手動ループ」です。 しかし、イテレータの管理が複雑になりがちです。