よく使うコード断片を集めた記事。
個人的な備忘録の側面が強い。
実用性の高いコード例を重視しているため、純粋なコードスニペットではなく余分な処理が大量に混ざっている点に注意して欲しい。
しかしこちらの方が実際の使用状況を思い浮かべやすいと思う。
まあ、エラー処理はバッサリ省略してるんだけどな。
REPL(Read-Eval-Print-Loop)
入力を読み込み、評価して、文字列として結果を返す。
インタプリタ作成の第一歩となる部分だが、書いたことが無いと意外ともたつく部分でもある。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
#include <iostream> #include <string> #include <vector> auto evaluate(const std::string& s) { return s; } int main() { std::vector<std::string> history {}; for (std::string buffer {}; std::cout << "< ", std::getline(std::cin, buffer); history.push_back(buffer)) { std::cout << evaluate(buffer) << std::endl; } return 0; } |
単純ながら非常に実用性の高いコードスニペット。
正直これだけで凡百のCコードをぶっちぎれるコーディング速度と安全性を獲得できる。
条件式の部分でカンマ演算子を利用して文字列の出力を同時に行っているが、実際に条件式の結果として評価されるカンマ演算子の右辺、
std::getline は返り値として与えられた入力ストリームへの参照を返す。
標準入力ストリームは
bool型への暗黙キャスト演算子オーバーロードを提供しているため、最終的な条件式の評価対象は入力ストリームのステータスである(
std::iosを参照されたし)。
for文の第2オペランドの出力部分を以下のように置き換えることでユーザビリティが向上するのも非常に良い。
std::cout << "[" << std::size(history) << "]< ",
Dispatch Table
いわゆる string switch を実現するコード。
コードの部品化、組織化という観点から非常に強力。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
#include <functional> #include <iostream> #include <unordered_map> int main() { std::unordered_map<std::string, std::function<int (int, int)>> dispatch_table {}; dispatch_table["+"] = [](auto lhs, auto rhs) { return lhs + rhs; }; dispatch_table["-"] = [](auto lhs, auto rhs) { return lhs - rhs; }; dispatch_table["*"] = [](auto lhs, auto rhs) { return lhs * rhs; }; dispatch_table["/"] = [](auto lhs, auto rhs) { return lhs / rhs; }; std::cout << dispatch_table.at("+")(100, 10) << std::endl; // 110 std::cout << dispatch_table.at("-")(100, 10) << std::endl; // 90 std::cout << dispatch_table.at("*")(100, 10) << std::endl; // 1000 std::cout << dispatch_table.at("/")(100, 10) << std::endl; // 10 return 0; } |
ハッシュマップを使用しているため、文字列比較の if 文を並べたコードに比べて分岐については爆速になっているはずだが(分岐数が大きく、キーとなる文字列が長いほど有効だと思う)、関数オブジェクトクラスを経由して処理を呼び出す部分のオーバーヘッドがどれほどのものになっているかはちょっと計測してみないとわからない。
テーブルに格納する関数のシグニチャは当然ながらすべて同じものでなければならないが、実用上、バラバラなシグニチャの関数を格納したいような場合は無いと言っていいだろう。
とはいえ、実際に格納する処理にはジェネリックラムダが使えるので幾らかの手抜きは可能である。
純粋な効率の観点から言えば、 std::unordered_mapのイニシャライザリスト内にラムダ式を並べるのが良いのだが、個人的にはこちらの方が読みやすいので好み。
シグニチャが異なる関数をその場でオーバーロードするスニペットに関しては後述する Overloaded Lambdas を参照して欲しい。
より実践的な例を以下に例示しておく。
勘の良い人は何のためのコード断片なのかすぐに気づけるだろう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
#include <functional> #include <iostream> #include <numeric> #include <string> #include <unordered_map> #include <vector> int main() { std::unordered_map<std::string, std::function<std::string (const std::vector<std::string>&)>> dispatch_table {}; dispatch_table["+"] = [](const auto& s) { return std::to_string( std::accumulate(std::begin(s) + 1, std::end(s), 0, [](auto& lhs, auto& rhs) { return lhs + std::stoi(rhs); }) ); }; // S式 (+ 1 1 2 3 5 8 13) をトークナイズしたもの、のつもり const std::vector<std::string> s {"+", "1", "1", "2", "3", "5", "8", "13"}; std::cout << dispatch_table.at(s[0])(s) << std::endl; // 出力は33 return 0; } |
Overloaded Lambdas
C++17 がもたらした感動的なほどに素晴らしいコード。
オーバーロードとラムダ式の無限の可能性を感じさせてくれる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
#include <iostream> template <typename... Ts> class overloaded_lambdas : public Ts... { public: using Ts::operator()...; }; // Deduction Guide template <typename... Ts> overloaded_lambdas(Ts&&...) -> overloaded_lambdas<Ts...>; int main() { overloaded_lambdas print_type { [&](int n) { std::cout << "int: " << n << std::endl; }, [&](char c) { std::cout << "char: " << c << std::endl; }, [&](float n) { std::cout << "float: " << n << std::endl; } }; int hoge {42}; print_type(hoge); char fuga {'c'}; print_type(fuga); float piyo {3.14}; print_type(piyo); return 0; } |
かつて同等の動作を実現させるためには、かなりの量のテンプレートメタプログラムを書く必要があった。
しかしそれはもう必要ない。
静的型付け言語のジェネリックプログラミングの次の時代を感じる。
これを使いこなせれば有象無象の軟弱な動的型付け言語を消し飛ばすことが出来ると信じている。
型名の表示を例としたが、これは型による分岐が可能であるという事を示したかっただけで、型名を文字列で取得するには
typeid(hoge).name()を使ったほうがずっとスマートだろう。
まあ、正直
typeid、
type_info、
type_indexあたりはクセが強すぎるのであまり好きではない。
それと、当然のことだが、オーバーロードなので引数の数も型も完全に自由だ。
引数を2つ取るラムダと引数を3つ取るラムダが混在していてもいいし、そもそも関数オブジェクトであればラムダである必要すら無い。
ジェネリックラムダも使える上、オーバーロードの解決は通常の規則に従うため、ジェネリックラムダに対してより具体的な型を指定した関数のほうが優先される。
つまり、次のようなフザケたコードすら実現可能なのだ。素晴らしきかなC++。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
#include <iostream> template <typename... Ts> class overloaded_lambdas : public Ts... { public: using Ts::operator()...; }; // Deduction Guide template <typename... Ts> overloaded_lambdas(Ts&&...) -> overloaded_lambdas<Ts...>; int main() { overloaded_lambdas sum { [](auto&& a, auto&& b, auto&& c) constexpr { return a + b + c; }, [](float lhs, float rhs) constexpr { return lhs + rhs + 3.14; }, std::plus<int> {} }; // std::plus<int> sum: 3 std::cout << "std::plus<int> sum: " << sum(1, 2) << std::endl; // float sum: 6.14 std::cout << "float sum: " << sum(float {1.0}, float {2.0}) << std::endl; // hello, world! std::cout << sum(std::string {"hello, "}, std::string {"world"}, std::string {"!"}) << std::endl; // sum: 6.280000! std::cout << sum( std::string {"sum: "}, std::to_string( sum(float {1.14}, float {2.0}) ), std::string {"!"} ) << std::endl; return 0; } |
ちなみに、using 宣言でのテンプレートパラメータパックの展開が許されていなかったり、Class Template Argument Deduction Guide が無かった頃には、同等のことをするために以下のようなコードを書く必要があった。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
template <typename... Ts> class overloaded_lambdas; template <typename T> class overloaded_lambdas<T> : public T { public: overloaded_lambdas(T&& lambda) : T {std::forward<T>(lambda)} {} using T::operator(); }; template <typename T, typename U, typename... Ts> class overloaded_lambdas<T, U, Ts...> : public T, public overloaded_lambdas<U, Ts...> { public: overloaded_lambdas(T&& lambda, U&& arg, Ts&&... args) : T {std::forward<T>(lambda)}, overloaded_lambdas<U, Ts...> {std::forward<U>(arg), std::forward<Ts>(args)...} {} using T::operator(); using overloaded_lambdas<U, Ts...>::operator(); }; template <typename... Ts> constexpr auto overload(Ts&&... args) -> overloaded_lambdas<typename std::decay<Ts>::type...> { return { std::forward<Ts>(args)... }; } |