glazeで環境変数から構造体に値を読み取る
前回の続きです。 glazeのリフレクション機能がだんだん分かってきたので、すこし実用的なコードを書いてみました。
やりたいこと
集成体の各メンバーに環境変数から値を取得して代入する
という関数を書いてみようと思います。
struct test_struct { int number1; float number2; std::string message; };
という構造体を定義しているとして、
NUMBER_1, NUMBER_2, MESSAGEという環境変数の値を取得して、対応する型変換をして代入する、というのが期待する動作です。
- フィールドの名前を取得する
- フィールドの型を確認する
- 環境変数値が文字列で取得できるので、変換処理を書いて代入する
というのを全フィールドに対して実装する必要がありますね。 毎回これを書くのはめんどうなのでglazeの機能でできるだけ実装してしまおうという話です。
メンバー名を一覧表示する
前回の投稿のおさらいでメンバーの一覧を取得して表示する関数を作ってみます。 メタ情報あり・なしの両方で一覧表示できるようにしてみます。
#include <iostream> #include "glaze/glaze.hpp" struct test_struct { int number1; float number2; std::string message; }; template<> struct glz::meta<test_struct> { using T = test_struct; static constexpr auto value = glz::object( "NUMBER_1", &T::number1, "NUMBER_2", &T::number2, "MESSAGE", &T::message ); }; template<typename T, std::size_t IDX = 0> auto print_members(T const& value) { if constexpr (IDX < glz::reflect<T>::size) { auto const field_name = glz::reflect<T>::keys[IDX]; std::cout << IDX << " " << field_name << '\n'; print_members<T, IDX + 1>(value); } } auto main([[maybe_unused]] int argc, [[maybe_unused]] char* argv[]) -> int { auto value = test_struct{}; print_members(value); }
実行結果は以下のとおり。 フィールド名がとれていることが確認できます。
0 NUMBER_1 1 NUMBER_2 2 MESSAGE
泥臭くてすみませんが、再帰関数での実装しか思いつきませんでした。 きっともっと綺麗な方法があるはず。昔の方法しか知らないのです…。
メンバーのサイズを取得した上で0から順に増加させていき、そのインデックスのメンバー情報を取得しています。
メンバーの値も表示する
これも前回の投稿の応用ですね。 メタ情報のあるなしで実装方法が変わるのでif constexprで分岐させています。
#include <iostream> #include "glaze/glaze.hpp" struct test_struct { int number1; float number2; std::string message; }; template<> struct glz::meta<test_struct> { using T = test_struct; static constexpr auto value = glz::object( "NUMBER_1", &T::number1, "NUMBER_2", &T::number2, "MESSAGE", &T::message ); }; template<typename T, std::size_t IDX = 0> auto print_members(T const& value) { if constexpr (IDX < glz::reflect<T>::size) { auto const field_name = glz::reflect<T>::keys[IDX]; auto const& field_value = [&]() -> decltype(auto) { if constexpr (glz::reflectable<T>) { // メタ情報がない場合はstd::tupleにしてから取得する return glz::get<IDX>(glz::to_tie(value)); } else { // メタ情報がある場合はメタ情報を利用して取得する auto& member = glz::get<IDX>(glz::reflect<T>::values); return glz::get_member(value, member); } }(); std::cout << IDX << " " << field_name << " " << field_value << '\n'; print_members<T, IDX + 1>(value); } } auto main([[maybe_unused]] int argc, [[maybe_unused]] char* argv[]) -> int { auto value = test_struct{.number1 = 55, .number2 = 33.3f, .message = "Hello", }; print_members(value); }
実行結果は以下の通り。 ちゃんと値取れてますね。
0 NUMBER_1 55 1 NUMBER_2 33.3 2 MESSAGE Hello
環境変数から値を読み取って代入する
メンバーの値を取得できるようになったので、今度は逆をやってみます。 フィールド値と同じ名前の環境変数があればそれを読み取って代入する、という関数を書いてみます。
- 環境変数の取得は
std::getenvを使います - 文字列から各型への変換は
std::from_charsを使います - 文字列への変換は
std::from_charsは対応していないので、文字列型の場合は直接代入します
他の型も試したいのでtest_structに文字列メンバーを追加してみます。
#include <iostream> #include <cstdlib> #include "glaze/glaze.hpp" struct test_struct { int number1; float number2; std::string message; }; template<> struct glz::meta<test_struct> { using T = test_struct; static constexpr auto value = glz::object( "NUMBER_1", &T::number1, "NUMBER_2", &T::number2, "MESSAGE", &T::message ); }; // print_members関数は前述のコードと同じため省略 namespace internal { template<typename T, std::size_t IDX> auto from_env(T& result) { if constexpr (IDX < glz::reflect<T>::size) { auto const field_name = glz::reflect<T>::keys[IDX]; if (auto const env = getenv(field_name.data()); env != nullptr) { auto& value = [&]() -> decltype(auto) { if constexpr (glz::reflectable<T>) { // メタ情報がない場合はstd::tupleにしてから取得する return glz::get<IDX>(glz::to_tie(result)); } else { // メタ情報がある場合はメタ情報を利用して取得する auto& member = glz::get<IDX>(glz::reflect<T>::values); return glz::get_member(result, member); } }(); // std::from_charsはstd::stringへの変換に対応していないので、std::stringの場合は直接代入する if constexpr (std::is_convertible_v<decltype(value), std::string>) { value = env; } else { // TODO: エラーチェックをすること std::from_chars(env, env + std::strlen(env), value); } } from_env<T, IDX + 1>(result); } } } template<typename T> auto from_env() { T result{}; internal::from_env<T, 0>(result); return result; } auto main([[maybe_unused]] int argc, [[maybe_unused]] char* argv[]) -> int { auto result = from_env<test_struct>(); print_members(result); }
環境変数を設定して実行してみます。
$ NUMBER_1=55 NUMBER_2=33.3 MESSAGE=Hello test_program
実行結果は次の通りです。
0 NUMBER_1 55 1 NUMBER_2 33.3 2 MESSAGE Hello
ちゃんと環境変数から値を読み取って代入できていますね。 glazeのメタ情報だけ定義すれば、あとは関数呼び出し1つで環境変数から構造体に値を読み取って代入できるようになりました。
足りないことはたくさんある
まだまだ足りない点はたくさんありますね。
実装長くなるし、glazeのリフレクションの話からも外れるので、この記事ではここまでとします。
- エラーハンドリングが全然ない
- 変換に失敗した場合の処理を書いていない
- ネストした構造体や配列・ベクターなどのコンテナに対応していない
この実装を拡張しつつ、実用的なコードにしていきたいと思います。 ある程度まとまったら公開したいですね。
まとめ
最低限のやりたいことは達成できたので良しとします。
- glazeのリフレクション機能を使うと、構造体のメンバー名や型情報を取得できる
- 任意の構造体のメンバーの名前と値を取得・表示する関数が書けた
- メタ情報があれば、メンバーの名前を変更してアクセスすることもできた
- メンバー名と一致する環境変数から値を取得して、構造体に代入する関数が書けた
glazeを使って構造体への読み書きが簡単にできるようになったので、他のフォーマットにも応用できそうです。 何かのパーサーと組み合わせて使うのも面白そうですね。






