読んできたプログラミング本

割と今まで読んできた本を聞かれることが多いので、ちょっと思い出そうとしてみる。記憶から漏れているものもあるかもしれないので思い出したら足すかも知れない。

私はC++の前にCを少しだけやっていたので、記憶はそこから始まる。

続きを読む

暗黙の型変換の落とし穴

こんなツイートを見た。

答えはboolだ。その理由は、こういう一見自明なクイズは大抵一番正しそうに見える解答が誤答だから、ではなく、暗黙の型変換の優先準位によるものだ。

まず、"Hello!"の型はstd::stringではない。これは文字列リテラルなので、その型はconst char[]だ。上記のコードにおいては、これがまず先頭を指すポインタ(const char*)へ変換され、ポインタがboolへ変換される。std::stringは標準ライブラリといえどユーザー定義型であることは間違いないので、built-inな型のほうが選択され、これはboolになる。

いや、確かに、この挙動はクソだ。100人に聞いたら100人が、これはstd::stringになるべきだと答えるだろう。私もそう思う。この挙動はライブラリがどうにかできるものでもないし、ユーザー定義型への変換を優先する方が問題が生じる場合もありそうで、仕方ないと言えば仕方ない気もするが……。

挙動が直感的でないのに対して、解決策は単純だ。文字列リテラルからstd::stringへの暗黙の型変換に頼らず、型を明示すればよい。つまり、std::string_literals::operator""sを使えばよい。

using std::string_literals::operator""s;
std::variant<std::string, int, bool> v = "Hello!"s;

これなら型はstd::stringになる。

以前、あるプロジェクトが使っているクラスがboolへの型変換関数を持っていることを知らず、そのクラスがboolへ変換された後にintに変換されることでオーバーロードが謎の方向に解決されるという事案が発生して死んでいたことがある。今回の件ではそれを思い出した。

emptyとsize==0

コンテナが空(要素を持っていない)かどうかを確認するとき、タイトルの2つのメソッドのどちらを使うかという話題で界隈が盛り上がっていたようだ。

私はempty()一択だ。サイズが0かどうかを確認したいなら、size() == 0を使うべきだ。だが、コンテナが空であることを確認できるメソッドがある場合に、コンテナが空であるかを確認するために別のコードを書く理由がない。 その2つが異なる場合があるのか、というのは疑わしい。ただ、未初期化領域を区別できるようなコンテナを作って、過去のSTLの命名を全て無視して関数を命名したなら、場合によっては筋が通った形でemptyかつsize != 0という結果になることはありえるだろう。sizeはnon-zeroだが、確保してある全領域が未初期化のままなのでempty、というように。

C++では実際には、リストのようにsizeを取得するコストが定数でない可能性のあるデータ構造を考えてemptyが作られたのだろうと思われるが、list::sizeが定数時間とされてからはこの点に関してはどうでもよくなった。

問題があるとすれば、emptyの命名だろうか。C++界隈では人々は長年STLに親しんでいるのでこの名前の関数がどう機能するかについて合意しあっているが、そうでない人がこれを見た時にどう反応するかはわからない。 もしこれが、is_emptyempty?のような命名であれば、オブジェクトの状態を取得し、boolを返すというところまで含意できたのだが。

というわけで以下のようにしよう。

#define is .
std::vector<int> vec;
std::cout << std::boolalpha << vec is empty() << std::endl;

言うまでもないがこのコードは冗談だ。

大掴みBoost.PolyCollection

この間、Boost 1.65がリリースされた。リリースノートをざっと見た所、新規ライブラリがいくつか入っている。そのPolyCollectionなるものが気になったのでざっと見てみることにした。これはその覚え書きである。

PolyCollectionは、多相型向けのコンテナだ。基底クラスを指定してその派生クラスを格納したり、anyによってDuckTypingしたり、std::functionに関数ポインタや関数オブジェクトを放り込んだりする際にいちいち生じるヒープアロケーション、それによるキャッシュヒット率の低下と、仮想関数呼び出し時の分岐予測の失敗を解決する。

続きを読む

座標ベクトルと汎化について

私の書くプログラムでは、趣味でも仕事でも、低次元(大抵は2か3次元)でのベクトルを扱うことが多い。そうなると普通は座標ベクトルを意味する構造体を作って管理するだろう。

続きを読む

FizzBuzz

会話をしていると、FizzBuzzの話になった。何かの拍子にFizzBuzzを書いてくださいと言われたらどうするだろうか、と思ってちょっと書いてみることにした。たまには気楽に書けるものもよい。

FizzBuzzを自然に捉えると、数字を文字列に変換していくルールに従って、入ってくる数字の列を変換し順に出力するというものだろう。

ということは、iota的なイテレータtransform_iteratorで覆って、ostream_iteratorに投げ込んでいけばいい。

iota的なことをしてくれるイテレータはないかと探したところ(作れるが演算子定義などが面倒だ)、boost::iterator::counting_iteratorがよいとわかった。

#include <boost/iterator/transform_iterator.hpp>
#include <boost/iterator/counting_iterator.hpp>
#include <algorithm>
#include <iostream>
#include <string>

int main(int argc, char** argv)
{
    if(argc != 2)
    {
        std::cerr << "usage: ./fizzbuzz <maximum>" << std::endl;
        return 1;
    }

    const auto fizzbuzz = [](const std::uint64_t i){
        using namespace std::literals::string_literals;
        if(i%3 == 0 && i%5 == 0) return "FizzBuzz"s;
        else if(i%3 == 0)        return "Fizz"s;
        else if(i%5 == 0)        return "Buzz"s;
        else                     return std::to_string(i);
    };

    std::copy(
        boost::make_transform_iterator(
            boost::make_counting_iterator(1ull),
            fizzbuzz),
        boost::make_transform_iterator(
            boost::make_counting_iterator(
                std::stoull(argv[1]) + 1ull),
            fizzbuzz),
        std::ostream_iterator<std::string>(std::cout, ", "));
    std::cout << std::endl;
    return 0;
}

こんなもんだろう。しかしまあ、1からmaxまでの数字の列を作って、それに関数を適用するだけだというのに非常に面倒なことをしないといけないのはやはり辛い。これくらい簡単になってもよいと思うのだが。

#include <algorithm>
#include <iostream>
#include <string>

int main(int argc, char** argv)
{
    if(argc != 2)
    {
        std::cerr << "usage: ./fizzbuzz <maximum>" << std::endl;
        return 1;
    }

    const auto fizzbuzz = [](const std::uint64_t i){
        if(i%3 == 0 && i%5 == 0) return "FizzBuzz"s;
        else if(i%3 == 0)        return "Fizz"s;
        else if(i%5 == 0)        return "Buzz"s;
        else                     return std::to_string(i);
    };
    const auto max = std::stoull(argv[1]);
    (1ull ... max).transform(fizzbuzz).output(std::cout, ", ");
    return 0;
}

と、思った辺りでBoost.rangeの存在を思い出した。

#include <boost/range/algorithm.hpp>
#include <boost/range/irange.hpp>
#include <boost/range/adaptor/transformed.hpp>
#include <iostream>
#include <cstdint>

int main(int argc, char** argv)
{
    if(argc != 2)
    {
        std::cerr << "usage: ./fizzbuzz <maximum>" << std::endl;
        return 1;
    }

    const auto fizzbuzz = [](const std::uint64_t i){
        using namespace std::literals::string_literals;
        if(i%3 == 0 && i%5 == 0) return "FizzBuzz"s;
        else if(i%3 == 0)        return "Fizz"s;
        else if(i%5 == 0)        return "Buzz"s;
        else                     return std::to_string(i);
    };

    boost::copy(boost::irange(1ull, std::stoull(argv[1]) + 1ull) |
                boost::adaptors::transformed(fizzbuzz),
                std::ostream_iterator<std::string>(std::cout, ", "));
    std::cout << std::endl;
    return 0;
}

やはりこうするとかなりマシだ。

関数型言語としてのC++ Template MetaProgramming入門

C++ではtemplateを使ってコンパイル時に計算を行うことができる。 今回は、関数型言語としての視点からC++テンプレートメタプログラミング(C++-TMP)の簡単な紹介をやってみたい。

注:私はHaskelllispの経験がホンの少しだけあるが、どちらも使いこなせるようになっているわけではない。のでこの記事には関数型への誤解が含まれる恐れがあることを注意していただきたい。

続きを読む