C++1z char_traitsのconstexpr対応

C++1zでのstring_viewの追加にともない、その実装に必要なstd::char_traitsクラスの一部メンバ関数constexprに対応します。

constexpr対応するのは、以下のメンバ関数です:

static constexpr int compare(const char_type* s1, const char_type* s2, size_t n);
static constexpr size_t length(const char_type* s);
static constexpr const char_type* find(const char_type* s, size_t n, const char_type& a);

参照

お断り

この記事の内容は、C++1zが正式リリースされる際には変更される可能性があります。正式リリース後には、C++日本語リファレンスサイトcpprefjpの以下の階層の下に解説ページを用意する予定です。

C++1z 値のコピー省略を保証

関数の戻り値のコピーを発生させない手法として、RVO (Return Value Optimization) やNRVO (Named Return Value Optimization) といった最適化がありました。

// RVOの最適化が動作した場合
struct Foo {};

Foo foo()
{
    return Foo();
}

Foo x = foo(); // Foo型のコピーコンストラクタが動作することなくxが初期化される
// NRVOの最適化が動作した場合
struct Foo { int value = 0; };

Foo foo()
{
    Foo y;
    y.value = 42;
    return y;
}

Foo x = foo(); // Foo型のコピーコンストラクタが動作することなくxが初期化される

しかし、これらの最適化はコンパイラに対して許可された動作であって、そのように最適化されることが保証されるものではありません。そのため、実際には(N)RVOによってコピーは起こらないけどコピーコンストラクタは用意しなければならない、といったことになります。

C++1zでは、このようなコピー省略を保証する仕組みが導入されます。そのため、オブジェクトの初期化のために使用するのであれば、コピーもムーブもできない型であっても、関数の戻り値として返せるようになります。

// C++1z
struct Foo {
    // Fooはコピーもムーブもできない
    Foo() = default;
    Foo(const Foo&) = delete;
    Foo(Foo&&) = delete;
};

Foo foo()
{
    return Foo();
}

Foo y = foo(); // OK

このコピー省略のためには、C++11で右辺値参照を導入するときに規定された「値カテゴリー (value category)」の仕様を利用します。prvalueという一時オブジェクトを表すカテゴリーの値を、オブジェクトの初期化のために使用する場合に、コピーが省略されるという仕様になります。

参照

お断り

この記事の内容は、C++1zが正式リリースされる際には変更される可能性があります。正式リリース後には、C++日本語リファレンスサイトcpprefjpの以下の階層の下に解説ページを用意する予定です。

更新履歴

整数を、任意の基数の文字列に変換する

除算と剰余で作れる。テーブルを拡張すれば何進数でもいける。

#include <iostream>
#include <cassert>
#include <string>
#include <sstream>

template <class Integer>
std::string to_base_string(Integer x, int base)
{
    assert(base >= 2 && base <= 16);

    if (x == 0)
        return "0";

    const std::string table = "0123456789abcdef";
    std::string result;
    Integer y = x;
    do {
        Integer div = y / base;
        Integer mod = y % base;

        result = table[mod] + result;
        y = div;
    }
    while (y != 0);

    return result;
}

int main()
{
    std::cout << to_base_string(254, 2) << std::endl;
    std::cout << to_base_string(9, 8) << std::endl;
    std::cout << to_base_string(254, 16) << std::endl;
}

出力:

11111110
11
fe

浮動小数点数を2の乗数で割る

a/bをする場合、bが2の乗数であれば「aの指数 - log2(b)」で除算ができます。

#include <iostream>
#include <bitset>
#include <cstdint>
#include <cassert>
#include <cmath>

union SingleFloat {
    float value;
    struct {
        int fraction : 23;
        int exponent : 8;
        bool sign : 1;
    } parts;
};

SingleFloat divide(SingleFloat a, std::uint32_t b)
{
    // 2の乗数のみ受け付ける
    assert(std::bitset<32>(b).count() == 1);

    SingleFloat result;
    result.parts.fraction = a.parts.fraction;
    result.parts.exponent = a.parts.exponent - std::log2(b);
    result.parts.sign = a.parts.sign;
    return result;
}

SingleFloat make_by_float(float x)
{
    SingleFloat result;
    result.value = x;
    return result;
}

int main()
{
    // 8/4 == 2
    std::cout << divide(make_by_float(8.0f), 4).value << std::endl;

    // 9/4 == 2.25
    std::cout << divide(make_by_float(9.0f), 4).value << std::endl;

    // 24/8 == 3
    std::cout << divide(make_by_float(24.0f), 8).value << std::endl;
}

出力:

2
2.25
3

ここでは、aが0、非正規化数、無限大、NaNの場合については考慮していません。

追記(2016/12/27 17:45)

もうちょっと真面目に実装し、上述した未考慮のケースに対応したコードはこちら:

#include <iostream>
#include <bitset>
#include <cstdint>
#include <cassert>
#include <cmath>

union SingleFloat {
    float value;
    struct {
        int fraction : 23;
        int exponent : 8;
        bool sign : 1;
    } parts;
};

SingleFloat make_by_float(float x)
{
    SingleFloat result;
    result.value = x;
    return result;
}

SingleFloat divide(SingleFloat a, std::uint32_t b)
{
    // 2の乗数のみ受け付ける
    assert(std::bitset<32>(b).count() == 1);

    if (std::isnan(a.value))
        return make_by_float(a.value);

    if (std::isinf(a.value))
        return make_by_float(a.value);

    int exponent_x = a.parts.exponent;
    int exponent_y = std::log2(b);
    if (exponent_x <= exponent_y) {
        // 0や非正規化数のように指数が小さいaをなにかの値で割るとアンダーフローするので、0を返す
        return make_by_float(0.0f);
    }
    else {
        SingleFloat result;
        result.parts.fraction = a.parts.fraction;
        result.parts.sign = a.parts.sign;
        result.parts.exponent = exponent_x - exponent_y;
        return result;
    }
}

int main()
{
    std::cout << " 8/4 = " << divide(make_by_float(8.0f), 4).value << std::endl;
    std::cout << " 9/4 = " << divide(make_by_float(9.0f), 4).value << std::endl;
    std::cout << "24/8 = " << divide(make_by_float(24.0f), 8).value << std::endl;

    std::cout << " 0/2 = " << divide(make_by_float(0.0f), 2).value << std::endl;
    std::cout << "-0/2 = " << divide(make_by_float(-0.0f), 2).value << std::endl;

    using limits = std::numeric_limits<float>;
    std::cout << "denorm_min/2 = " << divide(make_by_float(limits::denorm_min()), 2).value << std::endl;
    std::cout << "NaN/2 = " << divide(make_by_float(-limits::quiet_NaN()), 2).value << std::endl;
    std::cout << "inf/2 = " << divide(make_by_float(limits::infinity()), 2).value << std::endl;
    std::cout << "-inf/2 = " << divide(make_by_float(-limits::infinity()), 2).value << std::endl;
}

出力:

 8/4 = 0
 9/4 = 0
24/8 = 0
 0/2 = 0
-0/2 = 0
denorm_min/2 = 0
NaN/2 = -nan
inf/2 = inf
-inf/2 = -inf

参照

Boost 1.63.0がリリースされました

Boost 1.63.0がリリースされました。リリースノートはいつものように、翻訳・情報補完したものをboostjpサイトで公開しています。

このバージョンは、まだリリースされていないVisual C++ 2017には対応していませんのでご注意ください。

前バージョンである1.62.0のリリースが遅れたこともあり、1.63.0がリリースされるまでの期間は短めになっていました。そのため、小規模な修正版になっています。

Nが2の何乗かを調べる

C++11から標準数学ライブラリに入ったstd::log2()関数を使えば、Nが2の何乗かを取得できます。

#include <iostream>
#include <cmath>

int main()
{
    std::cout << std::log2(4.0) << std::endl;
    std::cout << std::log2(8.0) << std::endl; // 8は2の3乗
    std::cout << std::log2(16.0) << std::endl; // 16は2の4乗
    std::cout << std::log2(32.0) << std::endl;
    std::cout << std::log2(64.0) << std::endl;
    std::cout << std::log2(128.0) << std::endl;
    std::cout << std::log2(256.0) << std::endl;

    std::cout << std::log2(24.0) << std::endl;
    int x = 24;
    if (x <= 0 || (x & (x - 1)) != 0) { // xが2の乗数か判定
        std::cout << "24 is not power of 2" << std::endl;
    }
}

出力:

2
3
4
5
6
7
8
4.58496
24 is not power of 2

修正履歴

  • 2016/12/26 19:20 : 2の乗数かの判定に、std::modf()を使用していましたが、
double integral_part;
if (std::modf(std::log2(24.0), &integral_part) != 0) {
    std::cout << "24 is a not power of 2" << std::endl;
}

@haxeさんに教えていただいて、以下のように修正しました。

int x = 24;
if ((x & (x - 1)) != 0) {
    std::cout << "24 is not power of 2" << std::endl;
}
  • 2016/12/26 21:06 : uskzさんからの指摘を受け、例としてxstd::numeric_limits::min()の場合に2の乗数と判定されてしまうため、暫定対処として正の値かどうかのチェックを追加しました。
  • 2016/12/26 23:24 : uskzさんからの指摘を受け、2の乗数と判定するコードに0を許可していたところを、許可しないよう修正しました。

C++11 標準ストリームへの出力はスレッドセーフ

本記事の本題とする、「一回の書き込みにすれば排他制御は必要ない」という部分が誤りであったため、本記事を取り下げます。

C++11から標準ライブラリに、並行プログラミングのための各種機能が入った影響は、入出力のライブラリにもあります。スレッドの存在を前提とした規定が追加されています。

[iostream.objects.overview] 27.4.1 p4には、以下のようにあります:

Concurrent access to a synchronized (27.5.3.4) standard iostream object’s formatted and unformatted input (27.7.2.1) and output (27.7.3.1) functions or a standard C stream by multiple threads shall not result in a data race (1.10). [ Note: Users must still synchronize concurrent use of these objects and streams by multiple threads if they wish to avoid interleaved characters. —end note ]

同期された標準iostreamオブジェクトの書式化された・されていない入力と出力の関数、および標準Cストリームに対する複数スレッドによる並行アクセスは、データ競合を引き起こさない。[注: 交互的な文字を避けるには、これらのオブジェクトとストリームの並行使用を複数のスレッドで同期させておく必要がある。]

最初の一文での「同期」とは、並行プログラミングでの同期ではなく、標準入力と標準出力の同期のことを指します。デフォルトで同期されます。パフォーマンスのために同期を意図的に外すこともあります。

注釈のところにある「交互的な文字」とは、スレッド1がos << 1 << 2;、スレッド2がos << 3 << 4;と書き込む場合に、出力順が13241234のように不定になり、スレッド間の出力が混在することを指します。複数スレッドからの並行アクセスによってiostreamのオブジェクトがおかしな状態になったりはしませんが、複数回の書き込みでの順序保証はありません。

結果が交互的になることを避けたい場合には、os << 1 << 2;os << 3 << 4;をそれぞれ一度で書き込む必要があります。そのためには、文字列フォーマットの関数(Boost.Formatや、fmtlibなど)や、文字列ストリームなどを使用することで解決します。

void thread1()
{
    // 解決策1 : 文字列フォーマット
    os << fmt::format("{0}{1}", 1, 2);
}

void thread2()
{
    // 解決策2 : 文字列ストリーム
    std::stringstream ss;
    ss << 3 << 4;
    os << ss.str();
}

このようにすることで、出力は必ず12343412のどちらかになります。もちろん、ミューテックスを使用して一連の書き込みの順序を保証してもかまいません。

参照

C++1z if constexpr文

C++1zから、コンパイル時条件によって分岐するif constexpr文が導入されます。これにより、再帰やヘルパ関数を書かなくて済むケースが多くなります。

D言語にあるstatic if文のようなものです。

template <class T, class... Rest>
void g(T&& p, Rest&&... rs)
{
    if constexpr (sizeof...(rs) > 0) {
        g(rs...); // rs...が空のときのオーバーロードが不要
    }
}

elseの方にはconstexprは必要ありません。 テンプレート内で条件分岐した場合は、到達しなかったブロックはインスタンス化されません。

ただし、条件式内でのコンパイル時条件については全てインスタンス化され、短絡評価によって後ろの方の条件がインスタンス化されないことを期待するようなコードは書けないので注意してください。

if constexpr (has_value_type_v<T> && typename T::value_type())

このような条件式は、else節に進む場合にコンパイルエラーになります。このような条件を書きたい場合は、条件分岐を入れ子にする必要があります。

if constexpr (has_value_type_v<T>) {
    if constexpr (typename T::value_type()) {
        …
    }
}

なお、constexpr ifでなくif constexprになっているのは、前者を採用すると構文として、else ifを書くときにconstexprが二重に必要になってしまうのを避けるためです。

// 採用された構文
if constexpr (cond) {
}
else if constexpr (cond) {
}
else {
}
// 却下された構文
constexpr if (cond) {
}
constexpr else constexpr if (cond) {
}
constexpr else {
}

最初に考えられていたstatic if宣言と違って、if constexpr文はスコープを導入します。また、型や関数を定義する条件分岐には使用できません。

// こういうことはできない。
// 条件によって関数や型の宣言・定義を変えるようなことはできないし、
// クラススコープでif constexpr文は使用できない
struct X {
    if constexpr (cond) {
        void f();
        using int32 = int;
    }
    else {
        void g();
    }
};

参照

お断り

この記事の内容は、C++1zが正式リリースされる際には変更される可能性があります。正式リリース後には、C++日本語リファレンスサイトcpprefjpの以下の階層の下に解説ページを用意する予定です。

C++1z 非推奨だった古い例外仕様を削除

C++11でnoexceptが導入されたことによって非推奨になっていたthrowキーワードによる関数の例外仕様が、C++1zで削除されます。

throwキーワードによる例外仕様とは、void f() throw(std::runtime_error)のように、その関数がどの種類の例外を送出する可能性があるかを列挙する機能です。Javaの検査例外に近いものです。これはジェネリックプログラミングと相性が悪いことで問題視されていました。

C++1z以降、throwキーワードを使用した例外仕様のコードはコンパイルが通らなくなりますのでご注意ください。throw()noexceptと等価な効果を持つ機能として、非推奨のまま残ります。例外送出のthrowは変わりません。

// C++11から非推奨だがC++14まで合法。C++1zからコンパイルエラー
//void f() throw(std::runtime_error);

// C++11からは、noexceptを付けないことにより
// 「なんらかの例外が送出される可能性がある」ことを表現するか、
void f();

// コンパイル時の条件式によって、なんらかの例外が送出される可能性の有無を指定する
void f() noexcept(cond);

throwキーワードによる例外仕様の削除にともない、関連して非推奨だった以下の機能が削除されます:

これらの機能は、例外仕様で指定されていない例外が送出された場合に発生するエラーを検知するためのものですが、noexceptキーワードによる例外仕様では、例外は送出されるかされないかのみで、なんの例外が送出される可能性があるかは扱わないため、この種のエラーは発生しません。

参照

修正履歴

  • コメント欄での指摘を受け、throw()を使用したコードがC++1zからコンパイルエラーになる記述を削除。throw()が非推奨なまま残ることを記載

お断り

この記事の内容は、C++1zが正式リリースされる際には変更される可能性があります。正式リリース後には、C++日本語リファレンスサイトcpprefjpの以下の階層の下に解説ページを用意する予定です。

C++1z 例外仕様を型システムの一部にする

C++1zからは、noexceptによる関数の例外仕様が、型の一部として扱われるようになります。

互換性のためにnoexceptな関数ポインタから非noexceptな関数ポインタには変換できます。しかし、非noexceptな関数ポインタからnoexceptな関数ポインタには、変換できません。

void f() noexcept;
void g();

void(*p1)() noexcept = f;   // OK
void(*p2)() = f;            // OK : noexceptから非noexceptへの変換
//void(*p3)() noexcept = g; // コンパイルエラー : 非noexceptからnoexceptに変換できない

これは、ラムダ式も同様になります。

void(*p1)() noexcept = []() noexcept {}; // OK
void(*p2)() = []() noexcept {};          // OK
//void(*p3)() noexcept = [](){};         // コンパイルエラー

noexceptの違いによって関数をオーバーロードすることはできません。(戻り値の型と同様)

この仕様による破壊的な影響として、C++14まで有効だった以下のコードは、C++1zではコンパイルエラーになります:

void g1() noexcept;
void g2();

template<class T>
int f(T*, T*);

int x = f(g1, g2); // コンパイルエラー : g1とg2の型が一致しない (関数テンプレートの推論失敗)

参照

お断り

この記事の内容は、C++1zが正式リリースされる際には変更される可能性があります。正式リリース後には、C++日本語リファレンスサイトcpprefjpの以下の階層の下に解説ページを用意する予定です。