こんにちは。トイロジックの中堅プログラマーのNです。本日は世間一般に言うC++Tipsとは少しだけ毛色の違う、普通にC++の基礎勉強をしていたらまず出会うことのない、不思議なC++コードのお話をしようかと思います。
C++の不思議:固定長配列の配列サイズ
みなさん、配列の要素数を指定するときにどういった方法を用いますか?
例えば、1つは礼儀正しくconstで定義する方法
があります。
int array[] = {1,2,3};
const int k_array_size = 3; // 配列サイズを手で入れておく
でも今回のテーマは「不思議なC++」ですので、こんな風に取得してみましょう。
template<typename ArrayType,unsigned int _Num>
constexpr unsigned int GetStaticArraySize(ArrayType(&)[Num])noexcept
{
return Num;
}
int array[] = {1,2,3};
int array_size = GetStaticArraySize(array); // 配列サイズを取得(この場合は3が返る)
不思議ですよね。人間が明示的に定義しなくても配列サイズを数えてくれるマジックのような関数の出来上がりです! 何やら見慣れない関数引数の書き方がされてるかと思いますが大事なのは実装方法ではなく、その関数を呼んだときにどういった動作をするのかです
一見夢の配列サイズ取得関数ですが残念ながら”引数に送る配列の定義が見えないところでは使えません“。すなわち所在不明の配列やポインタを引数にはできないのです。
じゃあ、どこで使うの?使えるの?と気になるでしょう。そのお答えは最後にまとめてさせていただきます。
C++の不思議:可変長引数 その1
不思議な配列サイズ取得の関数を見た後は、さらに不思議な関数をご紹介します。そのまえに今回のお話には少しだけ前置きが必要です。
皆さんは不思議に感じたことはありませんか?きっとおそらくプログラムを勉強していれば一度でも目にするであろうprintf
と呼ばれる関数があります。
printf("%d+%dの答えは%dです",1,2,1 + 2); // 1+2の答えは3です
皆さんが当たり前に使っているであろうこちらの関数にはとっても単純で素朴な”謎”を秘めています。それは、引数の数が無制限ということです!
printf("あいうえお"); // 引数の数は1個
printf("%d+%dの答えは%dです",1,2,1 + 2); // 引数は4個。
printf("%s:%s:%s:%s","A","B","C","D"); // 引数は5個
まさかすべての引数のパターンをオーバーロードで定義しているわけではありません。このように引数の数が不定の引数を”可変長引数“と呼び、C++は可変長引数を扱えるように設計されているのです。
C++の不思議:可変長引数 その2
前置きが終わりましたので、いよいよ不思議な関数をご紹介しようと思います! まずはもったいぶらずに実装を先に記します。
template < typename... Types >
constexpr unsigned int GetPackCnt()
{
return sizeof...(Types);
}
この関数を実行するとどうなるでしょうか?試してみましょう!
unsigned int size = GetPackCnt(); // 0
何度実行しても0しか返ってきません。かといって引数を送れるようにこの関数はできていません。どうすれば関数の動作を変えられるのでしょうか。
答えはこうです。
unsigned int size1 = GetPackCnt<int>(); // 1
unsigned int size2 = GetPackCnt<int,float,float>(); // 3
unsigned int size3 = GetPackCnt<double,float,int,int,int>(); // 5
皆さん、引数は()の中だけと思い込んでいませんか?実はこの関数は<>();の中に何らかのデータ型を入れることで、その要素数を取得することができる関数なのです!
さて、この関数の挙動を変えることはできましたがではいよいよこの関数をどこで使うのか、どこで使えるのか、が気になってくると思います。それも配列サイズの例と同じようにまとめで語らせていただきます!
不思議なコード達との付き合い方
では皆さんが気になっている疑問の答えです。これらの不思議なC++はどこで使用するのか、使用できるのか。答えは「使われることはほとんど無い」です。だからこそ、”普通に勉強していればまず遭遇しない”のですから。
「ほとんど」ですので、全くないわけではありません。例えばGetStaticArraySize
の例では不要なconst変数の削減に利用できます。
int array[] = {1,2,3};
for(int i = 0; i < GetStaticArraySize(array); ++i){ /* hogehoge */}
さらにはなんと、配列の要素数の指定にも使用できたりします。
int array[] = {1,2,3};
int array2[GetStaticArraySize(array)]; // arrayと同じ大きさの配列を作る
GetPackCnt
の例はさらに局所的に利用されます。それは例えば、可変長引数の"数"を調べる際に使われます。
template<typename ...Args>
void PackTest(Args&&... _args)
{
int pack_num = GetPackCnt<Args...>();
/*hogehoge*/
}
しかし、やはりそのどちらも局所的にしか使われないでしょう。では今回の記事を通して私は何を知らせたかったのでしょう。実用性のないマニアックな知識を見せびらかしたかっただけなのでしょうか?もちろん違います。
プログラミングには実用的なものと、非実用的なものが存在します。ある分野では優れたコーディングでも、別の分野では全くと言っていいほど役に立たなかったり、ひどい場合だと「アンチパターン」として非難される場合もあります。一概にプログラミングといっても適材適所、使うべき場所と使うべきではない場所が存在しているのです。
そして今回私が紹介した不思議なコード群はその上記どちらにも属さず、「使わなくても大丈夫だけど、使えると少しかっこいいコード達」です。皆さんはコードを書く際に、「かっこよさ」は気にしてますか?もちろん、チーム制作では共同作業しやすさが大事ですし、人が読みやすいのが最も大事なことです。今回紹介したようなマニアックなコードを書いたら間違いなくチームメンバーを混乱させますし、不必要に書くべきではないです。
ただ例えば趣味のプログラムだったり、個人プログラムだったり、練習のプログラムだったりに関しては書き方は自由です。それはいわば白紙のキャンバスのようなものですので、そこにどんな独創的なものを描いても誰にも咎められません!
プログラミングを手段ではなく芸術として、自己表現の一つとして書いてみたらいかがでしょうか?
最後に
いかがでしょうか。今回は普通に勉強していればまず遭遇しない、不思議なC++について少しだけ切り込んでみました。
きっとここで記述したような実装をゲーム制作で積極的に取り入れることはまずないと思います。ゲーム制作はチームで作るものですし、コードの奇抜さよりもわかりやすさ、拡張のしやすさが何よりも大事です。
ですが少なくともプライベートではそういった普段知りえないC++の不思議な実装たちを調べて実装してみると、プログラミングに対する考え方も少し変わってくるのではないでしょうか。