C++で getopt_long関数を使ってコマンドラインオプションをパースする

C++用のコマンドラインオプションパーサーライブラリはこれといった決定版が無く、仕方ないので標準の getopt_long関数を使う

(boostはこの世に存在しないが stdになぜか輸入だけはされてくる世界線に住んでいる者にとって)C++でなにかコマンドラインツールを作ろうとするとまず真っ先にひっかかる問題、それはコマンドラインオプションのパース処理である。いろんな人が作っているが、それぞれ出来ることが違うので「ハイこれを選んでおけばとにかくOK」というのが無い。仕方ないので glibcの[getopt_long](https://linuxjm.osdn.jp/html/LDP_man-pages/man3/getopt.3.html)関数をなるべく(コピペで投入しても罪悪感を覚えない程度の)短い追加コードで C++フレンドリーにしてやろうと悪あがきをした結果の産物をここに捨てておく[^1]。 指定するコールバック関数のタイプで各オプションにおける引数の要件(引数なし、任意、必須の3種類)を自動的に決定できるのが特徴なのだが、ラムダ ```[](const std::optional<std::string>& optarg){}``` は ```std::function<void(const std::optional<std::string>&)>``` と ```std::function<void(const std::string&)>``` のどちらに該当するのか型推論では区別できない(?というのが正しいのかわからないがとにかくコンパイルエラーになる)ようなので ```std::optional``` 付きの方は明示的にキャストしてやる必要がある。 ```c++ /** * 標準の getopt_long 関数を C++から使いやすくするやつ */ #include <memory.h> #include <getopt.h> #include <string> #include <vector> #include <map> #include <functional> #include <optional> #include <variant> std::vector<std::string> getopt( int argc, char* argv[], const std::vector<std::tuple< std::optional<char>/*shortopt*/, std::optional<std::string>/*longopt*/, std::variant< std::function<void(void)>, // 0: no arg std::function<void(const std::optional<std::string>&)>, // 1: optional string arg std::function<void(const std::string&)> // 2: required string arg >/*func*/ >>& opts) { std::string shortopts; std::vector<struct option> longopts; std::map<std::string,std::variant< std::function<void(void)>, std::function<void(const std::optional<std::string>&)>, std::function<void(const std::string&)> >> funcs; for (const auto& opt:opts) { if (std::get<0>(opt).has_value()) { char shortopt = std::get<0>(opt).value(); const auto& func = std::get<2>(opt); shortopts += shortopt; if (std::holds_alternative<std::function<void(const std::optional<std::string>&)>>(func)) shortopts += "::"; else if (std::holds_alternative<std::function<void(const std::string&)>>(func)) shortopts += ":"; funcs[std::string(1, shortopt)] = func; } if (std::get<1>(opt).has_value()) { const auto& longopt = std::get<1>(opt).value(); const auto& shortopt = std::get<0>(opt); const auto& func = std::get<2>(opt); auto arg_required = std::holds_alternative<std::function<void(const std::optional<std::string>&)>>(func)? optional_argument : ((std::holds_alternative<std::function<void(const std::string&)>>(func))? required_argument : no_argument); longopts.push_back((struct option) { longopt.c_str(), arg_required, 0, shortopt.has_value()? shortopt.value() : 0 }); funcs[longopt] = func; } } struct option* clongopts = new struct option[longopts.size() + 1]; struct option* p = clongopts; for (const auto& lo:longopts) { memcpy(p, &lo, sizeof(*p)); p++; } memset(p, 0, sizeof(*p)); int c; int longindex = 0; while ((c = getopt_long(argc, argv, shortopts.c_str(), clongopts, &longindex)) >= 0) { const auto func = funcs.find(c == 0? clongopts[longindex].name : std::string(1,(char)c)); if (func != funcs.end()) { if (std::holds_alternative<std::function<void(const std::optional<std::string>&)>>(func->second)) { std::get<1>(func->second)(optarg? std::optional<std::string>(optarg) : std::nullopt); } else if (std::holds_alternative<std::function<void(const std::string&)>>(func->second)) { std::get<2>(func->second)(optarg? optarg : ""); } else { std::get<0>(func->second)(); } } } delete []clongopts; std::vector<std::string> non_option_args; for (int i = optind; i < argc; i++) { non_option_args.push_back(argv[i]); } return non_option_args; } #ifdef __MAIN__ // テスト用の main関数 // g++ -std=c++20 -D__MAIN__ getopt.cpp #include <iostream> int main(int argc, char* argv[]) { auto args = getopt(argc, argv, { // -a --aaaa with no arg {'a', "aaaa", []() { std::cout << "option -a(-aaaa) specified" << std::endl; }}, // -b --bbbb with optional arg {'b', "bbbb", (std::function<void(const std::optional<std::string>&)>)[](const auto& optarg) { if (optarg.has_value()) { std::cout << "option -b(--bbbb) with optional arg: " << optarg.value() << std::endl; } else { std::cout << "option -b(--bbbb) without optional arg" << std::endl; } }}, // -c --cccc with required arg {'c', "cccc", [](const std::string& optarg) { std::cout << "option -c(--cccc) with required arg: " << optarg << std::endl; }}, // -d without long form {'d', std::nullopt, []() { std::cout << "option -d specified" << std::endl; }}, // --eeee without short form {std::nullopt, "eeee", []() { std::cout << "option --eeee specified" << std::endl; }} }); for (const auto& arg:args) { std::cout << "arg: " << arg << std::endl; } return 0; } #endif ``` なお今日はこれを作って疲れたので本題には手を付けていない。 [^1]: 標準の getopt(_long)がなんでも出来るかというと全然そんなことはなく、最低限の機能しかないんだけど、そこは標準だからという理由で許せる。