初心者C++Advent Calendar 2017
この記事は初心者C++Advent Calendar 2017 10日目の記事です
本来もっとあとに記事を出す予定でしたがなんか空いているのとネタが手に入ったのでゆるふわ記事を出します。ガチな記事は12/15までお待ち下さい。
元ネタ
すでにC++でやっている人もいて
「強いエンジニアにHelloWorldさせてみた」をC++でやってみました。
ですね。
既存のルール
- ソースコードにダブルクォート、シングルクォート、数字を書くこと無く、「LIFULL」と出力して下さい。
- 最後に改行を付けること。
- 言語は問いません。
追加の縛り
- C++11で、C++14ではない
- 標準ライブラリのみ使用可、外部ライブラリ(boostとか)不可
- マクロ使用禁止
-
sizeof
演算子使用禁止 -
std::array
使用禁止 - enum/enum class使用禁止
- コンパイル時に文字列を組み立てる
- 文字コードがASCII互換(UTF-8とかCP932とか)、1byte=8bitな環境で動く
基本戦略
まあまず文字/文字列リテラルが書けず、数字も書けないのがもとの縛りです。この時点で普通のHelloWorldじゃない。
コンパイル時に文字列を組み立てる制約を追加したので実行時引数から文字列を渡すのはできません。
マクロは禁じたので先行例みたいに__LINE__
マクロは使えません。またsizeof
演算子も禁じたので配列の大きさから数字を作ることもできません。
幸いC++11縛りなので市民の義務であるconstexprが使えます。
というわけで愚直にASCIIの数値列を作ってやればいいことになります。
詳細
目的の文字列はASCIIでどういう数値列か
お題はLIFULL
なので76, 73, 70, 85, 76, 76, 0
となります。C/C++の文字列はNULL終端することをお忘れなく。
0と1をどう作るか
究極0と1さえ作れればあとはこねくり回してどうにかできます。
C++で0と1・・・といえばbool
型ですね!C++はbool
型から整数型に暗黙変換できる言語なので遠慮なく使いましょう!
constexpr char zero = false;
constexpr char one = true;
遠回りする手段としては
#include <type_traits>
constexpr char zero = std::true_type::value;
constexpr char one = std::false_type::value;
という手法やもっと遠回りしてC++14でなら
#include <limits>
constexpr char zero = std::numeric_limits<unsigned char>::min();
constexpr char one_impl()
{
char t = zero;
++t;
return t;
}
constexpr char one = one_impl();
という手もありますが、冗長なので素直にbool
を使います。
大きい数をどうやって作るか
まあひたすら足し算や掛け算で作ってもいいんですが、流石にだるいのでstd::numeric_limits
の力を借りましょう。
例えば63という数字は1byte=8bitを仮定できるので
constexpr char two = one + one;
constexpr sixty_three = std::numeric_limits<unsigned char>::max() >> two;
と表せます。
別解としては
PythonでHelloWorld(縛り)
の記事でされているように、76=2*2*19=2*2*(3*3*2+1)
を目指す方法もあります。
答え
あとは足し算やら引き算やら掛け算するだけですね。変数lifull
がコンパイル時に組み上げられた文字列が格納されています。
#include <iostream>
#include <limits>
constexpr char zero = false;
constexpr char one = true;
constexpr char two = one + one;
constexpr char seventy = (std::numeric_limits<unsigned char>::max() >> two) + (two * two * two) - one;
constexpr char seventy_three = seventy + two + one;
constexpr char seventy_six = seventy_three + two + one;
constexpr char eighty_five = seventy_six + two * two * two + one;
constexpr const char lifull[] = { seventy_six, seventy_three, seventy, eighty_five, seventy_six, seventy_six, zero };
int main()
{
std::cout << lifull << std::endl;
}
ありきたりな世界
個人的にHelloWorldと聞くとありきたりな世界なのでやってみましょう。
目指すのはarikitari_na_sekai
をデコードして97 114 105 107 105 116 97 114 105 95 110 97 95 115 101 107 97 105 0
です。
100を超える数字がいくつも並んでいます。流石にヘルパー関数がほしいところです。
constexpr char zero = false;
constexpr char one = true;
constexpr char two = one + one;
constexpr char three = two + one;
constexpr char four = two + two;
constexpr char five = four + one;
constexpr char six = four + two;
constexpr char seven = six + one;
constexpr char eight = four * two;
constexpr char nine = three * three;
constexpr char ten = eight + two;
constexpr char fmt(char n) { return n; }
constexpr char fmt(char n1, char n2) { return n1 * ten + n2; }
constexpr char fmt(char n1, char n2, char n3) { return fmt(n1 * ten + n2, n3); }
というわけでこんなものを。char
型なのでsigned/unsignedどちらにしろ4桁以上はないので可変長テンプレートを持ち出すまでもありません。
あとはよく出てくるa
とi
は別に作っておくと可読性が上がりそうです。
constexpr char a = fmt(nine, seven);
constexpr char i = fmt(one, zero, five);
というわけで完成品
#include <iostream>
constexpr char zero = false;
constexpr char one = true;
constexpr char two = one + one;
constexpr char three = two + one;
constexpr char four = two + two;
constexpr char five = four + one;
constexpr char six = four + two;
constexpr char seven = six + one;
constexpr char eight = four * two;
constexpr char nine = three * three;
constexpr char ten = eight + two;
constexpr char fmt(char n) { return n; }
constexpr char fmt(char n1, char n2) { return n1 * ten + n2; }
constexpr char fmt(char n1, char n2, char n3) { return fmt(n1 * ten + n2, n3); }
constexpr char a = fmt(nine, seven);
constexpr char i = fmt(one, zero, five);
constexpr const char arikitari_na_sekai[] = {
a, fmt(one, one, four), i, fmt(one, zero, seven), i, fmt(one, one, six), a, fmt(one, one, four), i,
fmt(nine, five),
fmt(one, one, zero), a,
fmt(nine, five),
fmt(one, one, five), fmt(one, zero, one), fmt(one, zero, seven), a, i,
zero
};
int main()
{
std::cout << arikitari_na_sekai << std::endl;
}