読者です 読者をやめる 読者になる 読者になる

無名構造体を名前付き 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);

みたいな感じで展開されるようにすれば便利なんじゃないかなぁと思ったら でちまるさんがすでにそれっぽいものを書いてました.さすがです.