実はC++はtupleクラスを使わずにタプル型やペア型の実現ができてしまいます。しかもラベル付きタプルですので、用途によってはtupleクラスよりも使い勝手は良いです。
無名構造体で代用
auto getDate() {
struct { int year, month, day; } date = {2016, 12, 24};
return date;
}
戻り値をauto
型にしている点が肝です。値受け取りの際もautoで受け取る必要があります。
auto date = getDate();
printf("%d年", date.year); // "2016年"
printf("%d月", date.month); // "12月"
printf("%d日", getDate().day); // "24日"
メンバ変数側のデストラクタもきちんと呼ばれますので、無名構造体側のメンバ変数にクラスや構造体を指定することも可能です。RVO等の最適化もしっかりと効きます。
C++17では構造体束縛によって、各メンバ変数を個別の自動変数に展開することもできます。
auto [year, month, day] = getDate();
printf("%d-%d-%d", year, month, day); // "2016-12-24"
引数として使う場合
取得したタプルを他の関数の引数として渡す際にはテンプレートを使用する必要があります。
template<class T> void printDate(const T& date) {
printf("%d-%d-%d\n", date.year, date.month, date.day);
}
printDate(getDate()); // "2016-12-24"
利用シーン
またこの手のコードを書かなくても良くなるのは大きな利点だと思います。
bool has_error;
auto it = open(&has_error);
if (!has_error) {
print(it);
}
こう書けるようになります。
auto it = open();
if (!it.has_error) {
print(it.data);
}
open(&has_error);
のような古臭いテクニックは、その場限りの小さなプログラムや使い捨てのプログラムを書く際によく使ってしまうのですが、今回のタプル・イディオムがあればこの手のコードがよりスッキリと書けるようになるので、相当便利なものになると思います。
C++は汎用化する必要のないような簡単なロジックを書く場合でも、やたらとクラスや列挙型を多用した壮大なコードを書かなければいけなかったり、冗長的で使いづらいライブラリの利用を強いられたりすることが多いので、この手の機能は大変重宝します。
応用
列挙体の使用やメンバ関数の使用も可能です。
メンバ関数、メンバ型の追加
auto getFile() {
enum class State { Err = 0x2 };
struct {
State state;
bool hasError() {
return state == State::Err;
}
} file = {State::Err};
return file;
}
auto x = getFile();
if (x.hasError()) puts("yes");
if (x.state == decltype(x.state)::Err) puts("yes");
Pair型
std::pair
のほうが使い勝手は良いですが、一応ペア型の実現も可能です。
template<class T, class U> auto make_pair(T t, U u) {
struct { T first; U second; } pair{t, u};
return pair;
}
auto pair = make_pair("Shop", 99);
printf("%s %d", pair.first, pair.second); // "Shop 99"
このようにテンプレートと組み合わせれば、構造体のメンバ型を動的に指定できるようになるため、色々と用途が広がるかと思います。プライベートなインナークラスを無名構造体で代用するような用途にも有効かもしれません。
まとめ
「# 引数として使う場合」からも分かるように、無名構造体は型情報が曖昧なため、引数型としてのやり取りが難しいという特徴があります。あくまでその場限りの簡易的なイディオムとして使うと良いでしょう。