無名構造体を名前付き tuple として使う
例えばユーザ名,Eメール,IDの3つの値を一気に返したいとき,C++ では std::tuple
が使えます.
#include <string> #include <tuple> using std::operator""s; auto get_user() { // std::tuple<std::string, std::string, int> return std::make_tuple("Linda"s, "foo@bar.com"s, 17); // name, email, id } // ... auto const user = get_user(); std::get<0>(user); // name std::get<1>(user); // email std::get<2>(user); // id
しかし,std::get<0>(user)
のようなコードをユーザコードとして書くとマジックナンバー 0
が出てきてしまい,「0番目の要素なんだっけ…」となります.
この解決方法としてまず enum
を使う方法が考えられます.
namespace user_info { enum { name = 0, email, id, }; } // ... auto const user = get_user(); std::get<user_info::name>(user); std::get<user_info::email>(user); std::get<user_info::id>(user);
また,std::tie
を使う方法もあります.
std::string name;
std::string email;
int id;
std::tie(name, email, id) = get_user();
ですが,enum
を使う方法では get_user()
の戻り値と user_info
内の enum
の対応関係がずれると実行時のバグを生んでしまいますし,毎度 std::get<>()
書くのは面倒です.また,std::tie
を使う方法は各変数を一度デフォルト初期化してから std::tie
の引数に渡す必要があるため面倒かつ非効率です.さらにauto &name = std::get<0>(user)
のように書くという方法もありますが,毎度参照を定義するのは面倒です.
他の言語のアプローチを見てみると,例えば Swift では Tuple
の要素に名前をつけられます.
let user = (name:"Linda", email:"foo@bar.com", id:17) user.name user.email user.id
C++ でもこれぐらい簡単に書けないかなぁと思って試しに無名構造体を使ってみました.こんな感じになります.
auto get_user() { struct { std::string name; std::string email; int id; } ret{ "Linda", "foo@bar.com", 17 }; return ret; } // ... auto const user = get_user(); user.name; user.email; user.id;
C++14 からは関数の戻り値型を auto
指定できるようになったため,関数内で定義した無名の構造体をそのまま return
することができます.
構造体定義が必須なので戻り値型を書くのが面倒ですが,get_user()
の戻り値の要素へのアクセスが user.name
のように簡潔かつ分かりやすくなります.
また,プリプロセッサマクロを使って
NAMED_STRUCT( (name, "Linda"s), (email, "foo@bar.com"s), (id, 17) );
が
[](auto &&... args) { struct { decltype("Linda"s) name; decltype("foo@bar.com"s) email; decltype(17) id; } ret {std::forward<decltype(args)>(args)...}; return ret; }("Linda"s, "foo@bar.com"s, 17);
みたいな感じで展開されるようにすれば便利なんじゃないかなぁと思ったら でちまるさんがすでにそれっぽいものを書いてました.さすがです.