cpprefjp更新: scoped_allocator完了

C++

<scoped_allocator>

<scoped_allocator>ヘッダのリファレンス作成が完了しました。

これで、全52ヘッダ中、40ヘッダのリファレンスが完成しました。残り12ヘッダです。

また、これによって「ユーティリティライブラリの完成」というマイルストーンが完了しました。現在、次のマイルストーンを何にするかを、以下のGitHub Issueにて検討中です:

文字列を二分割する

C++

split(s, delimiter)だとsの中にdelimiterが2つ以上現れたらその数だけ分割されてしまうので、区切り文字を一度だけ認識し、二分割する関数を書きました。

#include <iostream>
#include <string>
#include <utility>
#include <tuple>

std::pair<std::string, std::string>
    bisect(const std::string& s, char delimiter)
{
    std::size_t pos = s.find_first_of(delimiter);

    return std::make_pair(
               s.substr(0, pos),
               s.substr(pos + 1));
}

int main()
{
    std::string a, b;
    std::tie(a, b) = bisect("aaa:bbb", ':');

    std::cout << a << std::endl;
    std::cout << b << std::endl;
}

出力:

aaa
bbb

はじめはsplit2()という名前の関数にしようかと思っていたのですが、@dplusplusさんからbisect()という名前を提案してもらい、カッコ良かったのでそれにしました。

mapのemplace

C++

C++11標準ライブラリのコンテナには、emplace()emplace_back()といったメンバ関数が追加されました。これらの関数は、コンテナの要素型のコンストラクタ引数を受け取り、要素型のオブジェクトをemplace()emplace_back()の内部で一度だけ作る、という効率化の役割を果たしてくれます。

std::vectoremplace_back()なら、以下のように、要素型Xコンストラクタ引数を渡すという、シンプルな使い方ができます。

struct X {
    X(int a, int b, int c) {}
};

std::vector<X> v;
v.emplace_back(1, 2, 3);

しかし、std::mapemplace()は、使うのがちょっと難しいです。mapはキーと値の組を扱うので、要素を追加する際、それぞれのコンストラクタ引数を受け取ろうとすると、引数の区切りがどこなのかがわからないのです。

std::map<X, Y> m;

// どこまでがXの引数で、どこからがYの引数?
m.emplace(1, 2, 3, 4, 5, 6);

引数の区切りを明確にするためには、それぞれの引数をタプルにパックして転送する必要があります。結果として、std::mapemplace()は以下のように使います。

#include <iostream>
#include <map>

struct X {
    int a, b, c;
    X(int a, int b, int c)
        : a(a), b(b), c(c) {}
};

bool operator<(const X& l, const X& r)
{
    return std::tie(l.a, l.b, l.c) < std::tie(r.a, r.b, r.c);
}

struct Y {
    Y(int d, int e, int f) {}
};

int main()
{
    std::map<X, Y> m;

    m.emplace(
        std::piecewise_construct,
        std::forward_as_tuple(1, 2, 3),  // Xのコンストラクタ引数
        std::forward_as_tuple(4, 5, 6)); // Yのコンストラクタ引数
}

std::forward_as_tuple()関数を使うと、tuple<T1&&, T2&&, T3&&...>のような、右辺値参照のタプルができます。

std::pairコンストラクタに、そのようなタプルを展開して各要素を構築するためのオーバーロードが用意されているので、それにおまかせすることになります。pairのこの構築方法については、以下のエントリを参照:

C++03で、特定のメンバ関数が存在するかを判定する

C++

Boost.TTIのhas_member_function機能を使って、特定のメンバ関数を持っているかを判定する。

C++11のSFINAE Expressionを使えない環境向け。

#include <boost/tti/has_member_function.hpp>

struct X {
    int foo(double, char);
};

namespace my_namespace {
    // foo()メンバ関数を持っているか判定するためのメタ関数を定義する
    BOOST_TTI_HAS_MEMBER_FUNCTION(foo)
}

int main()
{
    static_assert(
        my_namespace::has_member_function_foo<
            X,                               // 対象の型
            int,                             // 戻り値の型
            boost::mpl::vector<double, char> // パラメータの型
         >::value == true,
        "must be true");
}

参照

ユーザー定義型を直接Fusionシーケンスにしない選択肢

C++

ユーザー定義型を直接Boost.Spirit.Qiのパース結果として使いたい場合には通常、ユーザー定義型をBOOST_FUSION_ADAPT_STRUCTでFusionシーケンスとして見なせるようにします。

最近、ユーザー定義型をFusionシーケンスに変換する関数を用意するのもアリじゃないかと思えてきました。以下のプログラムでは、Point型をFusionシーケンスに変換するas_fusion_sequence()関数を持たせています。

#include <iostream>
#include <string>
#include <boost/fusion/include/vector_tie.hpp>
#include <boost/spirit/include/qi.hpp>

namespace qi = boost::spirit::qi;
namespace fusion = boost::fusion;

struct Point {
    int x, y;

    fusion::vector<int&, int&> as_fusion_sequence()
    {
        return fusion::vector_tie(x, y);
    }
};

int main()
{
    const std::string input = "3,1";
    auto rule = boost::proto::deep_copy(qi::int_ >> ',' >> qi::int_);

    Point result;
    auto fused_result = result.as_fusion_sequence();

    std::string::const_iterator first = input.begin();
    if (qi::parse(first, input.end(), rule, fused_result)) {
        std::cout << result.x << ", " << result.y << std::endl;
    }
    else {
        std::cout << "parse error!" << std::endl;
    }
}

出力:

3, 1

こうしておくと、一つのユーザー定義型に対して、複数の解析方法(要素の順番を入れ替えたり)が定義できていい気がします。

Boost.Multiprecision 誤差の検証

C++

[Ruby]消費税計算にはBigDecimalを使いましょう

多倍長演算10進数型で演算を行えば、浮動小数点数の誤差がなくなるという話。C++の多倍長演算ライブラリであるBoost.Multiprecisionは無誤差であるというドキュメント上の規定はとくにないのですが、実際にどうなるのか検証してみました。

まずはdouble

まず、多倍長浮動小数型を使わず、doubleでやってみます。

#include <iostream>
#include <cmath>

int main()
{
    double a = 1800.0;
    double b = 1.08;

    double result = std::ceil(a * b);
    std::cout << result << std::endl; // 1944を期待する
}

出力:

1945

期待した1944ではなく、1945が出力されました。

cpp_dec_floatを使う

次に、Boost.Multiprecisionのcpp_dec_floatを使います。Boostのバージョンは1.55.0です。

#include <iostream>
#include <boost/multiprecision/cpp_dec_float.hpp>

namespace mp = boost::multiprecision;

using MyFloat = mp::cpp_dec_float_100;

int main()
{
    MyFloat a = 1800.0;
    MyFloat b = 1.08;

    MyFloat result = mp::ceil(a * b);
    std::cout << result << std::endl;
}

出力:

1945

こちらも期待した1944ではなく、1945になりました。

cpp_dec_floatを文字列初期化する

使い方が間違ってるみたいなので、値を文字列にしてみます。

#include <iostream>
#include <boost/multiprecision/cpp_dec_float.hpp>

namespace mp = boost::multiprecision;

using MyFloat = mp::cpp_dec_float_100;

int main()
{
    MyFloat a("1800.0");
    MyFloat b("1.08");

    MyFloat result = mp::ceil(a * b);
    std::cout << result << std::endl;
}

出力:

1944

期待した結果の1944になりました。

結び

最初にも書きましたが、この結果はcpp_dec_floatが無誤差演算であることを保証するものではありません。試しにやったらこういう結果になったというだけですので、ご注意を。

rationalはまだよくわかっていないので、使っていません。

Boost.Multiprecisionのnumberクラスのリファレンスページには、数学関数の演算でどれくらいの誤差が発生するかの表があるようです。

Non-member standard library function support - number class

生成するビット幅を指定可能なrandom_deviceクラスを書いた

C++

64ビット版メルセンヌ・ツイスターはいつ使うか」に関連して、64ビットのシードを作れるように、生成する整数型を指定できるrandom_deviceクラスを書きました。

Boost.Randomの実装をコピペしてきてちょっと変えただけです。

インタフェース:

// <shand/random/random_device.hpp>
namespace shand {
   template <class UIntType>
   class random_device {
     using result_type = UIntType;

     explicit random_device(const std::string& token = implementation-defined);
     result_type operator()();

     template <class Iterator>
     void generate(Iterator first, Iterator last);
   };

   using random_device_32 = random_device<std::uint32_t>;
   using random_device_64 = random_device<std::uint64_t>;
}

標準やBoost.Randomのものと違うのは、以下の点:

  • クラステンプレートになっている
  • テンプレートパラメータとして、生成する整数型をとる
  • entropy()メンバ関数がない
    • 有意義な結果は誰も返せないので、必要ないと判断した。

mt19937_64のシードを作るために、64ビット版random_device型であるrandom_device_64を使えます。

#include <iostream>
#include <cstdint>
#include <random>
#include <shand/random/random_device.hpp>

int main()
{
    shand::random_device_64 seed_gen;
    std::mt19937_64 engine(seed_gen());

    for (int i = 0; i < 10; ++i) {
        std::uint64_t result = engine();
        std::cout << result << std::endl;
    }
}

出力例:

3893701729893995308
10711700309771701362
17929754974751635565
17553023603653786827
13502565356887369052
10931010143106186937
4547535530895765923
153507564072994118
1189766131898797718
5246883299549427354

64ビット版メルセンヌ・ツイスターはいつ使うか

C++

C++11標準ライブラリとBoost.Randomには、32ビット版メルセンヌ・ツイスターmt19937と64ビット版のmt19937_64があって、64ビット版をいつ使えばいいかよくわからなかったのでメモ。

What is the advantage of using mt19937_64 over its 32 bit variant? - StackOverflow

  • 64ビット版は、より大きな幅(64ビット)のシードを持つことができる。
    • これによって、乱雑さがさらに増す。
  • CPUの64ビット命令を使うので、64ビットマシンなら速く動作して、32ビットマシンなら遅い。
  • sizeofはどちらも同じ。

ただし、random_deviceクラスは32ビット(unsigned int)の値を作るので、それをそのままシードとして使う場合は、64ビットシードにはならない。その実装詳細であるWindowsCryptGenRandom() APIUNIX系OS/dev/urandom自体は64ビットで読み込める。

シード生成の問題があるため、C++11とBoost 1.55.0時点では、ターゲット環境が64ビットならmt19937_64は高速に動作するので、そちらを使う。という、高速化のみに注目した選定基準でいいのかもしれない。