C++何もわからない
左辺値、左辺値参照、右辺値参照間の代入可否
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;
}
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()を呼び出して明示的にクリアする必要があります。
とのこと。
-
std::unique_ptrは仕様上コピーコンストラクタを提供していない ↩︎
パトリシア木を使った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;
}
値型を返す関数呼び出しの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が適用されない可能性があります
}
コンストラクタに渡す参照を不変参照として初期化したい
他でも使用するためムーブはできない。
コピーはしたくない。
できることなら可変参照としては渡したくない。
どうすればいい?
とChatGPT
に聞いた時の回答
std::reference_wrapper を使うことが最適です。std::reference_wrapper は、オブジェクトへの参照を保持し、コピーやムーブの際にも参照そのものをコピーします。これにより、コピーすることなく参照を保持し、他の場所でも利用できます。
加えて不変参照にしたい場合は?と聞いた。
その参照先が const かどうかは、std::reference_wrapper のテンプレート引数である型によって決まります。
std::reference_wrapper<const T>とすればいいみたい。
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"
としている。
テンプレートパラメータの別名
標準ライブラリで採用されている古い書き方では、
typedef typename Item = T; // テンプレートパラメータの別名
typedef typename ItemDep = T::Dep; // 依存型の別名
のように記述がされている。
今は、using
を使って記述できる。
using Item = T; // テンプレートパラメータの別名
using ItemDep = typename T::Dep; // 依存型の別名