Open7

C++何もわからない

ktz_aliasktz_alias

左辺値、左辺値参照、右辺値参照間の代入可否

Chat GPTに説明してもらいつつ作成してもらった(概ね問題なさそうだがいくつか怪しいところがありそう)。

項番 ターゲット ソース エラー/OK 説明
1 右辺値 const 左辺値 エラー const 左辺値から右辺値への代入はコンパイルエラーです。
2 右辺値 非const 左辺値 エラー const 左辺値から右辺値への代入はコンパイルエラーです。
3 右辺値 const 左辺値参照 エラー const 左辺値参照から右辺値への代入はコンパイルエラーです。
4 右辺値 非const 左辺値参照 エラー const 左辺値参照から右辺値への代入はコンパイルエラーです。
5 右辺値 const 右辺値参照 エラー const 右辺値参照から右辺値への代入はコンパイルエラーです。
6 右辺値 非const 右辺値参照 OK const 右辺値参照から右辺値への代入は問題ありません。
7 const 左辺値 const 左辺値 OK const 左辺値から const 左辺値への代入は問題ありません。
8 const 左辺値 非const 左辺値 OK const 左辺値から const 左辺値への代入は問題ありません。
9 const 左辺値 const 左辺値参照 OK const 左辺値参照から const 左辺値への代入は問題ありません。
10 const 左辺値 非const 左辺値参照 OK const 左辺値参照から const 左辺値への代入は問題ありません。
11 const 左辺値 右辺値 OK 右辺値から const 左辺値への代入は問題ありません。
12 const 左辺値 const 右辺値参照 発生し得ない const 右辺値参照から const 左辺値への代入は記述できません。
13 const 左辺値 非const 右辺値参照 OK const 右辺値参照から const 左辺値への代入は問題ありません。
14 非const 左辺値 const 左辺値 エラー const 左辺値から非 const 左辺値への代入はできません。
15 非const 左辺値 非const 左辺値 OK const 左辺値から非 const 左辺値への代入は問題ありません。
16 非const 左辺値 const 左辺値参照 エラー const 左辺値参照から非 const 左辺値への代入はできません。
17 非const 左辺値 非const 左辺値参照 OK const 左辺値参照から非 const 左辺値への代入は問題ありません。
18 非const 左辺値 右辺値 OK 右辺値から非 const 左辺値への代入は問題ありません。
19 非const 左辺値 const 右辺値参照 エラー const 右辺値参照から非 const 左辺値への代入はできません。
20 非const 左辺値 非const 右辺値参照 OK const 右辺値参照から非 const 左辺値への代入は問題ありません。
21 const 左辺値参照 const 左辺値 OK const 左辺値から const 左辺値参照への代入は問題ありません。
22 const 左辺値参照 非const 左辺値 OK const 左辺値から const 左辺値参照への代入は問題ありません。
23 const 左辺値参照 const 左辺値参照 OK const 左辺値参照から const 左辺値参照への代入は問題ありません。
24 const 左辺値参照 非const 左辺値参照 OK const 左辺値参照から const 左辺値参照への代入は問題ありません。
25 const 左辺値参照 右辺値 OK 右辺値から const 左辺値参照への代入は問題ありません。
26 const 左辺値参照 const 右辺値参照 OK const 右辺値参照から const 左辺値参照への代入は問題ありません。
27 const 左辺値参照 非const 右辺値参照 エラー const 右辺値参照から const 左辺値参照への代入はコンパイルエラーです。
28 非const 左辺値参照 const 左辺値 エラー const 左辺値から非 const 左辺値参照への代入はコンパイルエラーです。
29 非const 左辺値参照 非const 左辺値 OK const 左辺値から非 const 左辺値参照への代入は問題ありません。
30 非const 左辺値参照 const 左辺値参照 エラー const 左辺値参照から非 const 左辺値参照への代入はコンパイルエラーです。
31 非const 左辺値参照 非const 左辺値参照 OK const 左辺値参照から非 const 左辺値参照への代入は問題ありません。
32 非const 左辺値参照 右辺値 エラー 右辺値から非 const 左辺値参照への代入はコンパイルエラーです。ターゲットの方のライフサイクルが長くなるため
33 非const 左辺値参照 const 右辺値参照 発生し得ない const 右辺値参照から非 const 左辺値参照への代入は記述できません。
34 非const 左辺値参照 非const 右辺値参照 エラー const 右辺値参照から非 const 左辺値参照への代入はコンパイルエラーです。
35 const 右辺値参照 右辺値 エラー 右辺値から const 右辺値参照への代入はコンパイルエラーです。
36 const 右辺値参照 非const 右辺値参照 OK const 右辺値参照から const 右辺値参照への代入は問題ありません。
37 非const 右辺値参照 const 左辺値 エラー const 左辺値から非 const 右辺値参照への代入はコンパイルエラーです。
38 非const 右辺値参照 非const 左辺値 エラー const 左辺値から非 const 右辺値参照への代入はコンパイルエラーです。
39 非const 右辺値参照 const 左辺値参照 エラー const 左辺値参照から非 const 右辺値参照への代入はコンパイルエラーです。
40 非const 右辺値参照 非const 左辺値参照 エラー const 左辺値参照から非 const 右辺値参照への代入はコンパイルエラーです。
41 非const 右辺値参照 右辺値 エラー 右辺値から非 const 右辺値参照への代入はコンパイルエラーです。
42 非const 右辺値参照 const 右辺値参照 エラー const 右辺値参照から非 const 右辺値参照への代入はコンパイルエラーです。
43 非const 右辺値参照 非const 右辺値参照 OK const 右辺値参照から非 const 右辺値参照への代入は問題ありません。

実行例

#include <iostream>
#include <utility>  // std::move

class MoveOnly {
public:
    MoveOnly() {}
    MoveOnly(const MoveOnly&) = delete;
    MoveOnly& operator=(const MoveOnly&) = delete;
    MoveOnly(MoveOnly&&) = default;
    MoveOnly& operator=(MoveOnly&&) = default;
};

int main() {
    // 項番 1
    const int constLvalue1 = 1;
    // int&& rvalue1 = constLvalue1; // エラー

    // 項番 2
    int nonConstLvalue1 = 2;
    // int&& rvalue2 = nonConstLvalue1; // エラー
    int&& rvalue2 = std::move(nonConstLvalue1); // OK

    // 項番 3
    const int& constLvalueRef1 = constLvalue1;
    // int&& rvalue3 = constLvalueRef1; // エラー

    // 項番 4
    int& nonConstLvalueRef1 = nonConstLvalue1;
    // int&& rvalue4 = nonConstLvalueRef1; // エラー
    int&& rvalue4 = std::move(nonConstLvalueRef1); // OK

    // 項番 5
    const int&& constRvalueRef1 = 5;
    // int&& rvalue5 = constRvalueRef1; // エラー

    // 項番 6
    int&& nonConstRvalueRef1 = 6;
    int&& rvalue6 = std::move(nonConstRvalueRef1); // OK

    // 項番 7
    const int constLvalue2 = 7;
    const int constLvalueTarget2 = constLvalue2; // OK

    // 項番 8
    int nonConstLvalue2 = 8;
    const int constLvalueTarget3 = nonConstLvalue2; // OK

    // 項番 9
    const int& constLvalueRef2 = constLvalue2;
    const int constLvalueTarget4 = constLvalueRef2; // OK

    // 項番 10
    int& nonConstLvalueRef2 = nonConstLvalue2;
    const int constLvalueTarget5 = nonConstLvalueRef2; // OK

    // 項番 11
    int rvalue11 = 11;
    const int constLvalueTarget6 = std::move(rvalue11); // OK

    // 項番 12
    const int&& constRvalueRef2 = 12;
    const int constLvalueTarget7 = constRvalueRef2; // OK

    // 項番 13
    int&& nonConstRvalueRef2 = 13;
    const int constLvalueTarget8 = nonConstRvalueRef2; // OK

    // 項番 14
    const int constLvalue3 = 14;
    // int nonConstLvalueTarget1 = constLvalue3; // エラー

    // 項番 15
    int nonConstLvalue3 = 15;
    int nonConstLvalueTarget2 = nonConstLvalue3; // OK

    // 項番 16
    const int& constLvalueRef3 = constLvalue3;
    // int nonConstLvalueTarget3 = constLvalueRef3; // エラー

    // 項番 17
    int& nonConstLvalueRef3 = nonConstLvalue3;
    int nonConstLvalueTarget4 = nonConstLvalueRef3; // OK

    // 項番 18
    int rvalue18 = 18;
    int nonConstLvalueTarget5 = std::move(rvalue18); // OK

    // 項番 19
    const int&& constRvalueRef3 = 19;
    // int nonConstLvalueTarget6 = constRvalueRef3; // エラー

    // 項番 20
    int&& nonConstRvalueRef3 = 20;
    int nonConstLvalueTarget7 = nonConstRvalueRef3; // OK

    // 項番 21
    const int constLvalue4 = 21;
    const int& constLvalueRefTarget1 = constLvalue4; // OK

    // 項番 22
    int nonConstLvalue4 = 22;
    const int& constLvalueRefTarget2 = nonConstLvalue4; // OK

    // 項番 23
    const int& constLvalueRef4 = constLvalue4;
    const int& constLvalueRefTarget3 = constLvalueRef4; // OK

    // 項番 24
    int& nonConstLvalueRef4 = nonConstLvalue4;
    const int& constLvalueRefTarget4 = nonConstLvalueRef4; // OK

    // 項番 25
    int rvalue25 = 25;
    const int& constLvalueRefTarget5 = std::move(rvalue25); // OK

    // 項番 26
    const int&& constRvalueRef4 = 26;
    const int& constLvalueRefTarget6 = constRvalueRef4; // OK

    // 項番 27
    int&& nonConstRvalueRef4 = 27;
    // const int& constLvalueRefTarget7 = nonConstRvalueRef4; // エラー

    // 項番 28
    const int constLvalue5 = 28;
    // int& nonConstLvalueRefTarget1 = constLvalue5; // エラー

    // 項番 29
    int nonConstLvalue5 = 29;
    int& nonConstLvalueRefTarget2 = nonConstLvalue5; // OK

    // 項番 30
    const int& constLvalueRef5 = constLvalue5;
    // int& nonConstLvalueRefTarget3 = constLvalueRef5; // エラー

    // 項番 31
    int& nonConstLvalueRef5 = nonConstLvalue5;
    int& nonConstLvalueRefTarget4 = nonConstLvalueRef5; // OK

    // 項番 32
    int rvalue32 = 32;
    // int& nonConstLvalueRefTarget5 = std::move(rvalue32); // エラー

    // 項番 33
    const int&& constRvalueRef5 = 33;
    // int& nonConstLvalueRefTarget6 = constRvalueRef5; // 発生し得ない

    // 項番 34
    int&& nonConstRvalueRef5 = 34;
    // int& nonConstLvalueRefTarget7 = nonConstRvalueRef5; // エラー

    // 項番 35
    int rvalue35 = 35;
    // const int&& constRvalueRefTarget1 = std::move(rvalue35); // エラー

    // 項番 36
    int&& nonConstRvalueRef6 = 36;
    const int&& constRvalueRefTarget2 = std::move(nonConstRvalueRef6); // OK

    // 項番 37
    const int constLvalue6 = 37;
    // int&& nonConstRvalueRefTarget1 = constLvalue6; // エラー

    // 項番 38
    int nonConstLvalue6 = 38;
    // int&& nonConstRvalueRefTarget2 = nonConstLvalue6; // エラー
    int&& nonConstRvalueRefTarget2 = std::move(nonConstLvalue6); // OK

    // 項番 39
    const int& constLvalueRef6 = constLvalue6;
    // int&& nonConstRvalueRefTarget3 = constLvalueRef6; // エラー

    // 項番 40
    int& nonConstLvalueRef6 = nonConstLvalue6;
    // int&& nonConstRvalueRefTarget4 = nonConstLvalueRef6; // エラー
    int&& nonConstRvalueRefTarget4 = std::move(nonConstLvalueRef6); // OK

    // 項番 41
    int rvalue41 = 41;
    // int&& nonConstRvalueRefTarget5 = std::move(rvalue41); // エラー

    // 項番 42
    const int&& constRvalueRef7 = 42;
    // int&& nonConstRvalueRefTarget6 = constRvalueRef7; // エラー

    // 項番 43
    int&& nonConstRvalueRef7 = 43;
    int&& nonConstRvalueRefTarget7 = std::move(nonConstRvalueRef7); // OK

    return 0;
}
ktz_aliasktz_alias

std::vector<std::unique_ptr<T>>のマージ

2つのstd::vectorをマージする場合のイディオムは

std::vector<Foo> vec_1{...};
std::vector<Foo> vec_2{...};

vec_1.insert(vec_1.cend(), vec_2.begin(), vec_2.end());

なのだけど、要素がstd::unique_ptrでラップされている場合、std::vec::insertメソッドが要素のコピーコンストラクタを要求するため、メソッドがマッチしない的なコンパイルエラーとなる[1]

この事象に対しては以下のようにstd::move_iteratorでラップすることで解決できる。

vec_1.insert(vec_1.cend(), std::make_move_iterator(vec_2.begin()), std::make_move_iterator(vvec_2.end()));

別解

std::moveのオーバーロードを使用する。

std::move(vec_2.begin(), vec_2.end(), std::back_inserter(vec_1));

std::moveを使用する注意点として、Chat GPT先生曰く

std::make_move_iteratorを使う方法では、vec2の要素がvec1にムーブされた後、vec2の要素は空になります。
std::moveとstd::back_inserterを使う方法でも、vec2の要素がvec1にムーブされますが、vec2の要素は空ポインタのまま残ります。これを防ぐためには、vec2.clear()を呼び出して明示的にクリアする必要があります。

とのこと。

脚注
  1. std::unique_ptrは仕様上コピーコンストラクタを提供していない ↩︎

ktz_aliasktz_alias

パトリシア木を使ったAho-Corasickアルゴリズム

複数の候補の中から複数のパターンのいずれかを含むものを選択する適度に扱いやすそうなアルゴリズムはないかと。ChatGPT大先生に尋ねようとしたらタイミング悪くBadGatewayとなったため、Claude先生に浮気して聞いてみた。

何度かのやり取りで、

  • インデックス構造の使用
  • インバーテッドインデックス
  • n-gram インデックス
  • トライ木とAho-Corasickアルゴリズムの組み合わせ

を薦めてくれた。内容を聞くに

  • 一つ目は割とメモリ食う
  • 二つ目と三つ目は全文検索。そこまではいらんかな
  • 四つ目が手軽そう

「トライ木とAho-Corasickアルゴリズムの組み合わせ」をパトリシア木にできるって聞いたら、「いけるで」って返答をもらい、C++のコードを出してもらった。

パトリシア木とAho-Corasickアルゴリズムの組み合わせの初期案
#include <iostream>
#include <vector>
#include <queue>
#include <unordered_map>
#include <string>
#include <memory>

class PatriciaNode {
public:
    std::unordered_map<char, std::unique_ptr<PatriciaNode>> children;
    PatriciaNode* fail;
    bool is_output;
    std::string prefix;

    PatriciaNode() : fail(nullptr), is_output(false) {}
};

class PatriciaAhoCorasick {
private:
    std::unique_ptr<PatriciaNode> root;

public:
    PatriciaAhoCorasick() : root(std::make_unique<PatriciaNode>()) {}

    void addPattern(const std::string& pattern) {
        PatriciaNode* node = root.get();
        size_t i = 0;
        while (i < pattern.length()) {
            auto it = node->children.find(pattern[i]);
            if (it == node->children.end()) {
                auto newNode = std::make_unique<PatriciaNode>();
                newNode->prefix = pattern.substr(i);
                node->children[pattern[i]] = std::move(newNode);
                node = node->children[pattern[i]].get();
                break;
            } else {
                PatriciaNode* child = it->second.get();
                size_t j = 0;
                while (j < child->prefix.length() && i < pattern.length() && child->prefix[j] == pattern[i]) {
                    ++i;
                    ++j;
                }
                if (j == child->prefix.length()) {
                    node = child;
                } else {
                    // 分割が必要
                    auto splitNode = std::make_unique<PatriciaNode>();
                    splitNode->prefix = child->prefix.substr(0, j);
                    child->prefix = child->prefix.substr(j);
                    splitNode->children[child->prefix[0]] = std::move(it->second);
                    node->children[splitNode->prefix[0]] = std::move(splitNode);
                    node = node->children[splitNode->prefix[0]].get();
                    if (i < pattern.length()) {
                        auto newNode = std::make_unique<PatriciaNode>();
                        newNode->prefix = pattern.substr(i);
                        node->children[newNode->prefix[0]] = std::move(newNode);
                    }
                    break;
                }
            }
        }
        node->is_output = true;
    }

    void buildFailTransitions() {
        std::queue<PatriciaNode*> q;
        root->fail = root.get();
        for (auto& pair : root->children) {
            pair.second->fail = root.get();
            q.push(pair.second.get());
        }

        while (!q.empty()) {
            PatriciaNode* current = q.front();
            q.pop();

            for (auto& pair : current->children) {
                char ch = pair.first;
                PatriciaNode* child = pair.second.get();
                PatriciaNode* fail = current->fail;

                while (fail != root.get() && fail->children.find(ch) == fail->children.end()) {
                    fail = fail->fail;
                }

                if (fail->children.find(ch) != fail->children.end()) {
                    child->fail = fail->children[ch].get();
                } else {
                    child->fail = root.get();
                }

                child->is_output |= child->fail->is_output;
                q.push(child);
            }
        }
    }

    bool search(const std::string& text) {
        PatriciaNode* current = root.get();
        size_t i = 0;

        while (i < text.length()) {
            auto it = current->children.find(text[i]);
            if (it != current->children.end()) {
                PatriciaNode* child = it->second.get();
                size_t j = 0;
                while (j < child->prefix.length() && i < text.length() && child->prefix[j] == text[i]) {
                    ++i;
                    ++j;
                }
                if (j == child->prefix.length()) {
                    current = child;
                    if (current->is_output) {
                        return true;  // マッチしたらすぐに true を返す
                    }
                } else {
                    i -= j;
                    current = root.get();
                }
            } else {
                if (current == root.get()) {
                    ++i;
                } else {
                    current = current->fail;
                }
            }
        }
        return false;  // マッチがなければ false を返す
    }
};

class FilePathFilter {
private:
    PatriciaAhoCorasick ac;

public:
    FilePathFilter(const std::vector<std::string>& filterPatterns) {
        for (const auto& pattern : filterPatterns) {
            ac.addPattern(pattern);
        }
        ac.buildFailTransitions();
    }

    bool contains(const std::string& filePath) {
        return ac.search(filePath);
    }
};

int main() {
    std::vector<std::string> filters = {"documents", "downloads", ".txt"};
    std::vector<std::string> filePaths = {
        "/home/user/documents/file1.txt",
        "/home/user/downloads/file2.pdf",
        "/home/user/pictures/image.jpg"
    };

    FilePathFilter pathFilter(filters);

    for (const auto& path : filePaths) {
        if (pathFilter.contains(path)) {
            std::cout << "File path " << path << " matches a filter." << std::endl;
        } else {
            std::cout << "File path " << path << " does not match any filter." << std::endl;
        }
    }

    return 0;
}

ただし、検索で無限ループするケースがあるのと、どのノードもフェイル遷移先がルートノードになるまず味。
ここで回数切れとなったため、このコードを持ち帰ってChatGPT大先生と殴り合った。

フェイル遷移先がルートノードになる問題の対処

以下のように改変した。

void buildFailTransitions() {
    std::queue<PatriciaNode*> q;
    root->fail = std::nullopt; // ルートノードのフェイル遷移先はnullopt
    q.push(root.get()); // ルートノードそのものをキューに追加

    while (!q.empty()) {
        PatriciaNode* current = q.front();
        q.pop();

        for (auto& pair : current->children) {
            PatriciaNode* child = pair.second.get();
            std::optional<PatriciaNode*> fail = current->fail;

            // fail チェーンを辿って適切なフェイル遷移先を探す
            while (fail.has_value()) { // ルートノードのフェイル遷移のばあいのみループに入らない
                PatriciaNode* fail_node = *fail;
                if (fail_node->children.contains(child->prefix[0])) {
                    PatriciaNode* node = fail_node->children[child->prefix[0]].get();
                    if (child->prefix.starts_with(node->prefix)) {
                        child->fail = node;
                        break;
                    }
                }
                fail = fail_node->fail;
            }

            // 適切なフェイル遷移先が見つからない場合、ルートを設定
            if (!child->fail.has_value()) {
                child->fail = root.get();
            }

            q.push(child); // 子ノードをキューに追加
        }
    }
}

ポイントは、root->fail = std::nulloptにすること。
たいていroot->fail = rootとする例をよく見かけるが、おそらくトライ木向け。パトリシア木だとプレフィックスが複数文字のためうまくいかなかった。
while (fail.has_value())とすることで、ルートノード以外は少なくと1回はループに入るようにし、適切にフェイル遷移先が設定できるようになった。

あとルートノードはwhileループに入らないことを利用して、ルートノードをキューに入れて簡潔化。

検索で無限ループするケースの対処

以下のように改変した。

bool search(const std::string& text) {
    PatriciaNode* current = root.get();
    size_t i = 0;

    while (i < text.length()) {
        auto it = current->children.find(text[i]);
        if (it != current->children.end()) {
            PatriciaNode* child = it->second.get();
            // 現在のノードのプレフィックスをマッチング
            if (text.substr(i).starts_with(child->prefix)) {
                i += child->prefix.length();
                current = child;
                if (current->is_pattern_end && i == text.length()) {
                    return true;  // 完全なパターンマッチかつ文字列の終端の場合のみ true を返す
                }
                continue;
            } 
        }

        // マッチしなかった場合、現在の位置を戻す
        if (current == root.get()) {
            ++i;
        } else {
            current = current->fail;
        }
    }
    return false;
}

ポイントはノードにマッチせず、フェイル遷移先がルートノードの場合に変数iを進める。
これで無限ループが回避された。割と満足。

最終系は以下の感じ

パトリシア木とAho-Corasickアルゴリズムの組み合わせの最終系
void addPattern(const std::string& pattern) {
    PatriciaNode* node = root.get();
    size_t i = 0;

    while (i < pattern.length()) {
        auto it = node->children.find(pattern[i]);
        if (it == node->children.end()) {
            // 新しいノードを作成し、パターンの残りを追加
            auto newNode = std::make_unique<PatriciaNode>();
            newNode->prefix = pattern.substr(i);
            node->children[pattern[i]] = std::move(newNode);
            break;
        } else {
            // 既存のノードとプレフィックスのマッチング
            PatriciaNode* child = it->second.get();
            auto [prefixMismatch, textMismatch] = std::ranges::mismatch(child->prefix, pattern.substr(i));

            if (prefixMismatch == child->prefix.end()) {
                // プレフィックスが完全に一致する場合
                if (textMismatch == pattern.substr(i).end()) {
                    // パターンが完全に一致する場合
                    child->is_pattern_end = true;  // 現在のノードを終端とする
                    break;
                } else {
                    // ノードを更新し、次の文字に進む
                    node = child;
                    i += node->prefix.length(); // 既に一致した部分をスキップ
                }
            } else {
                // プレフィックスが部分一致する場合、分割が必要
                auto splitNode = std::make_unique<PatriciaNode>();
                splitNode->prefix = child->prefix.substr(0, std::distance(child->prefix.begin(), prefixMismatch));
                splitNode->is_pattern_end = false;  // 分割時に前方ノードを false に設定
                child->prefix = child->prefix.substr(std::distance(child->prefix.begin(), prefixMismatch));
                splitNode->children[child->prefix[0]] = std::move(it->second);
                node->children[splitNode->prefix[0]] = std::move(splitNode);
                node = node->children[splitNode->prefix[0]].get();
                
                if (textMismatch != pattern.substr(i).end()) {
                    auto newNode = std::make_unique<PatriciaNode>();
                    newNode->prefix = pattern.substr(std::distance(pattern.begin(), textMismatch));
                    node->children[newNode->prefix[0]] = std::move(newNode);
                }
                break;
            }
        }
    }
}

void buildFailTransitions() {
    std::queue<PatriciaNode*> q;
    root->fail = std::nullopt; // ルートノードのフェイル遷移先はnullopt
    q.push(root.get()); // ルートノードそのものをキューに追加

    while (!q.empty()) {
        PatriciaNode* current = q.front();
        q.pop();

        for (auto& pair : current->children) {
            PatriciaNode* child = pair.second.get();
            std::optional<PatriciaNode*> fail = current->fail;

            // fail チェーンを辿って適切なフェイル遷移先を探す
            while (fail.has_value()) { // ルートノードのフェイル遷移のばあいのみループに入らない
                PatriciaNode* fail_node = *fail;
                if (fail_node->children.contains(child->prefix[0])) {
                    PatriciaNode* node = fail_node->children[child->prefix[0]].get();
                    if (child->prefix.starts_with(node->prefix)) {
                        child->fail = node;
                        break;
                    }
                }
                fail = fail_node->fail;
            }

            // 適切なフェイル遷移先が見つからない場合、ルートを設定
            if (!child->fail.has_value()) {
                child->fail = root.get();
            }

            q.push(child); // 子ノードをキューに追加
        }
    }
}

bool search(const std::string& text) {
    PatriciaNode* current = root.get();
    size_t i = 0;

    while (i < text.length()) {
        auto it = current->children.find(text[i]);
        if (it != current->children.end()) {
            PatriciaNode* child = it->second.get();
            // 現在のノードのプレフィックスをマッチング
            if (text.substr(i).starts_with(child->prefix)) {
                i += child->prefix.length();
                current = child;
                if (current->is_pattern_end && i == text.length()) {
                    return true;  // 完全なパターンマッチかつ文字列の終端の場合のみ true を返す
                }
                continue;
            } 
        }

        // マッチしなかった場合、現在の位置を戻す
        if (current == root.get()) {
            ++i;
        } else {
            current = current->fail;
        }
    }
    return false;
}
ktz_aliasktz_alias

値型を返す関数呼び出しのstd::moveの使いどき

ChatGPT大先生に

C++の右辺値参照について
値型を戻り値として返す場合、returnでstd::moveすべきか呼び出し側でstd::moveすべきか、またはその両方なのか

って聞いた時の回答

C++で値型を戻り値として返す場合、return文でstd::moveを使うべきか、呼び出し側でstd::moveを使うべきかという疑問は、モダンC++のムーブセマンティクスとコピー省略(Return Value Optimization, RVO)の仕組みに関連しています。
結論

  • return文でstd::moveを使うべき場合があります。
  • 呼び出し側ではstd::moveは不要です(特別な理由がない限り)。

以下は、std::moveを使用しなくても、コンパイラによってRVOやNRVO(Named Return Value Optimization)が適用される。

T create_object() {
    T obj;
    return std::move(obj);
}

以下は、特別なケースとして、std::moveが必要

T create_object() {
    T obj;
    if (/* some condition */) {
        return obj;
    }
    return std::move(obj);
}

関数呼び出しの結果は右辺値参照のため、std::moveは不要

T obj = create_object();  // 呼び出し側でstd::moveは不要

以下のようにイニシャライザで結果を返す場合、std::moveは不要。
イニシャライザがそもそも右辺値参照であり、RVOによって最適化される。
逆にstd::moveをつけると、不要なムーブが発生したりして最適化を損ねる。

T create_object() {
    return T{...};  // または return {...};
}

以下のような多段の関数呼び出しでもstd::moveは不要。
逆にstd::moveをつけると、不要なムーブが発生したりして最適化を損ねる。

auto fn_a() -> SomeValue {
    return {...];
}
auto fn_b() -> SomeValue {
    return fn_a();
}

特別なケースとして、一度ローカル変数で受ける場合は、std::moveが必要。

auto fn_a() -> Some {
    Some obj = fn_b();
    return obj;  // この場合、NRVOが適用されない可能性があります
}
ktz_aliasktz_alias

コンストラクタに渡す参照を不変参照として初期化したい

他でも使用するためムーブはできない。
コピーはしたくない。
できることなら可変参照としては渡したくない。
どうすればいい?

ChatGPTに聞いた時の回答

std::reference_wrapper を使うことが最適です。std::reference_wrapper は、オブジェクトへの参照を保持し、コピーやムーブの際にも参照そのものをコピーします。これにより、コピーすることなく参照を保持し、他の場所でも利用できます。

加えて不変参照にしたい場合は?と聞いた。

その参照先が const かどうかは、std::reference_wrapper のテンプレート引数である型によって決まります。

std::reference_wrapper<const T>とすればいいみたい。

ktz_aliasktz_alias

linuxでclang

linux下でclang++でビルドする場合、何も指定しないとlibstdc++(gcc互換として)でリンクされる
LLVMのランタイムでマングリングさせたいときはlibc++を明示的に指定しなければならない。
またLLVMのパスをプレフィックスとして指定する必要もある。

        -DCMAKE_CXX_FLAGS="-stdlib=libc++ -L$BREW_PREFIX/llvm/lib" \
        -DCMAKE_EXE_LINKER_FLAGS="-stdlib=libc++ -L$BREW_PREFIX/llvm/lib" \

BREW_PREFIX="/home/linuxbrew/.linuxbrew/opt"としている。

ktz_aliasktz_alias

テンプレートパラメータの別名

標準ライブラリで採用されている古い書き方では、

typedef typename Item = T; // テンプレートパラメータの別名
typedef typename ItemDep = T::Dep; // 依存型の別名

のように記述がされている。

今は、usingを使って記述できる。

using Item = T; // テンプレートパラメータの別名
using ItemDep = typename T::Dep; // 依存型の別名