需要のないページ

プログラミングや趣味や。

Land of Lispを読んでいたのになぜかC++のコードを書いていた

前書き

悟りを得たくて、神の言語Lispに手を付けてみた。
もちろんここで紐解くのは、言わずと知れた聖典Land of Lispである。

Land of Lisp

Land of Lisp

 

この本はなかなか痛快で、他の言語のユーザを痛烈に皮肉っている。*1
Lispのシンプルさ*2の主張として、次のような例が取り上げられている(p.20)。

((foo<bar>)*(g++)).baz(!&qux::zip->ding())

 このコードはC++シンタックスに従っている。意図を強調するために、C++の中でも特に他の言語とは違う変てこりんなシンタックスをてんこ盛りにしてみた。
 [中略]
 もちろんこのC++プログラムは意味を持っている。このコードをC++のプログラムに埋め込めば、それはコンピュータに何か動作をさせるだろう。

...ほんとにこれ動くの?というわけで、試してみる。

まずは解析

どこがどう結合するのか、適当な結合表を見ながら考えてみる。
そのうち、今回の式に登場する演算子だけをまとめると次のようになる。

優先順位 演算子 名称 結合規則
1 :: スコープ解決演算子 左から右
2 a++ 後置インクリメント演算子
a() 関数呼び出し演算子
. メンバアクセス演算子(ドット演算子)
-> メンバアクセス演算子(アロー演算子)
3 ! 論理否定演算子 右から左
&a 単項アドレス演算子
5 a*b 乗算演算子 左から右

項目は C++ Operator Precedence - cppreference.com を、
日本語名称は C++ の演算子、優先順位と結合規則 を参照した。

さて、この表に基づくと、次の二式は同値である。
((foo<bar>)*(g++)).baz(!&qux::zip->ding())
((foo<bar>)*(g++)).baz(!(&(((qux::zip)->ding()))))
後者めっちゃくちゃ汚いな。まあ、これで検証に踏むべき手順は見えた。

実際に書いてみる:part1

まずは、bazの実引数部分について考えてみよう。

以下のコードは、全てG++でコンパイルが通る。*3
ただし、-Wunused-value(評価されない値)ワーニングは出るけどね。*4

quxスコープの中に変数zipを作る

zipの型は何でもいいので、空のクラスを作った。


    class Zip{};

    namespace qux {
        Zip zip;
    }
    
    int main(void) {
        qux::zip;
    }
    
zipからアロー演算子でdingを参照する

アロー演算子を適用出来るということは、zipがポインタであることを意味する。
それを踏まえて、先ほどのコードから少し宣言部分も修正してある。


    class Zip{
    public:
        void ding(void) {
        }
    } zip_;

    namespace qux {
        Zip *zip = &zip_;
    }
    
    int main(void) {
        qux::zip->ding();
    }
    
dingの戻り値のアドレスを解決する

dingは実体を持つ何らかのオブジェクトを返す必要がある。
手を抜くとしたら自分自身の参照を返すのが一番楽だろう。


    class Zip{
    public:
        Zip& ding(void) {
            return *this;
        }
    } zip_;

    namespace qux {
        Zip *zip = &zip_;
    }
    
    int main(void) {
        &qux::zip->ding();
    }
    
得られたアドレスの論理否定を取る

これに関しては特に書き換えるべき箇所はない。
また、zip_のアドレスがヌルであり得ない以上、必ず式の値はfalseになる。


    class Zip{
    public:
        Zip& ding(void) {
            return *this;
        }
    } zip_;

    namespace qux {
        Zip *zip = &zip_;
    }
    
    int main(void) {
        !&qux::zip->ding();
    }
    


実際に書いてみる:part2

次は、bazをメンバ関数として持っているオブジェクトを書く。
面倒なので、クラスZipをここでも利用してやろう。

テンプレート変数を用意する

テンプレートクラス/関数はよく見かけるが、テンプレート変数はあまり見ない。
なぜなら、この要素はC++14以降に追加されたからである。

変に説明するのも面倒なので、有益なリンクを貼っておく。
変数テンプレート - cpprefjp C++日本語リファレンス

これを踏まえると、次のように表現できるだろう。


    class Zip{
    };
    
    using bar = Zip;
    template<typename T> T foo;
    
    int main(void) {
        foo<bar>;
    }
    

エイリアスとは言え、クラス名がパスカルケースでないのは大分気持ち悪い。
まあ、次のように書いてもいいのだが。これはこれはなんか嫌だ。


    class Zip{
    };
    
    constexpr int bar = 0;
    template<int B> Zip foo;
    
    int main(void) {
        foo<bar>;
    }
    
gを定義する

説明不要だろう... と言いたいが。せっかくだ、gもZip型にしてしまおう。
この場合、後置インクリメントを自前でオーバーロードしないといけない。

今回初めて知ったのだが、こいつを定義する場合、ダミーの引数が必要だと言う。
C++マニアック - オペレータのオーバーロード | C++入門 C++言語講座

これは言語仕様上の欠陥じゃないか?C++ともあろう人が...
とりあえず組む。実用は度外視しているので、自身の参照を返せばよいだろう。

コンマ演算子はどんな状況でも使えるので、こういうときは重宝する。


    class Zip{
    public:
        Zip& operator++(int) {
            return *this;
        }
    } g;
    
    using bar = Zip;
    template<typename T> T foo;
    
    int main(void) {
        foo<bar>, g++;
    }
    
アスタリスク二項演算子を定義する

直前の内容を踏まえれば、今度こそ説明不要だろう。


    class Zip{
    public:
        Zip& operator++(int) {
            return *this;
        }
        Zip& operator*(Zip&) {
            return *this;
        }
    } g;
    
    using bar = Zip;
    template<typename T> T foo;
    
    int main(void) {
        (foo<bar>)*(g++);
    }
    


実際に書いてみる:マージ

part1とpart2の流れを汲むと、次のように問題が実現可能であることがわかる。
ただし、Zipに論理値を引数に取るメンバ関数bazを追加する必要がある。


    class Zip{
    public:
        Zip& ding(void) {
            return *this;
        }
        void baz(bool) {
        }
    
        Zip& operator++(int) {
            return *this;
        }
        Zip& operator*(Zip&) {
            return *this;
        }
    } zip_, g;
    
    namespace qux {
        Zip *zip = &zip_;
    }
    
    using bar = Zip;
    template<typename T> T foo;
    
    int main(void) {
        ((foo<bar>)*(g++)).baz(!&qux::zip->ding());
    }
    

bazの中身が空だと少し悲しい。
せっかく引数を取っているのだから、華の出力部分を任せよう。


    #include <iostream>
    
    class Zip{
    public:
        Zip& ding(void) {
            return *this;
        }
        void baz(bool arg) {
            if(arg) std::cout << "Lispは神の言語\n";
        }
    
        Zip& operator++(int) {
            return *this;
        }
        Zip& operator*(Zip&) {
            return *this;
        }
    } zip_, g;
    
    namespace qux {
        Zip *zip = &zip_;
    }
    
    using bar = Zip;
    template<typename T> T foo;
    
    int main(void) {
        ((foo<bar>)*(g++)).baz(!&qux::zip->ding());
    }
    

いざ動かしてみると... 何も表示されない。
そりゃそうだ。bazの引数は絶対にfalseになるんだから。

結論

前述の式は、確かに記述可能であったし、警告も何もない。
ただし、有用に利用出来ることは断じてないだろう。

それでも俺は無理矢理動かす

bazの中で論理値を反転させてしまうのが早いのだが。
せっかくなのでオーバーロードを悪用してみる。


    #include <iostream>
    
    class FooBar {
    public:
        bool operator&(void) {
            return false;
        }
    } foobar;
    
    class Zip{
    public:
        FooBar& ding(void) {
            return foobar;
        }
        void baz(bool arg) {
            if(arg) std::cout << "Lispは神の言語\n";
        }
    
        Zip& operator++(int) {
            return *this;
        }
        Zip& operator*(Zip&) {
            return *this;
        }
    } zip_, g;
    
    namespace qux {
        Zip *zip = &zip_;
    }
    
    using bar = Zip;
    template<typename T> T foo;
    
    int main(void) {
        ((foo<bar>)*(g++)).baz(!&qux::zip->ding());
    }
    

これでLispを礼賛できるぞ!... C++への冒瀆によってだが。

*1:例えば、Lisper以外のことをクロマニヨン人と称し、同様にLisp以外の言語をクロマニヨン言語と呼んでいる。

*2:すべての構文がリスト。これ以上ないほどシンプルなシンタックスを持っている。

*3:main関数にreturn文が存在しないが、これはC++の規格上一切問題ない。C++では、main関数に限ってはreturn 0が補完される。ただ、非常に気持ち悪い。

*4:main関数内の式の値を用いていないからで、これはしょうがない。