C++1zの言語拡張まとめ

2017年中に改訂される予定のC++14の次のバージョン、仮称C++1zの更新内容をまとめました。正式名称はISO/IEC 14882:2017、通称C++17になる予定です。

C++1zの概要

C++17は、C++11ほど大きな変更はありませんが、重要な言語機能(構造化束縛とか)や、広く適用できるライブラリ機能が多く入っています。ライブラリは、ファイルシステムが入るのが大きいですね。ようやく標準ライブラリのみで、ファイルサイズを取得したり、ファイルのコピーや移動などを扱えるようになります。

策定体制として、Study Group (SG)と呼ばれる専門家グループがたくさん作られ、そこで同時並行に議論、策定が進められていた仕様のうち、固まったものがいくつかC++1zで導入されます。

それぞれの専門家グループで考えられた仕様はTechnical Specification (TS)という単位で個別に各国の承認をとっており、コンパイラでもstd::exprerimental名前空間以下などで実験的に実装されていました。C++1zでは専門家グループで進められた仕様のうち、以下のものが入ります:

  • Library Fundamentals TS (基本的なライブラリ機能)
    • any, optional, string_view, メモリプール, 検索アルゴリズム, サンプリングアルゴリズム, タプルを展開して関数呼び出しするapply関数, shared_ptrの配列対応, 最大公約数と最小公倍数など
  • Filesystem TS (ファイルシステムのライブラリ)
  • Parallelism TS (並列ライブラリ)

C++1zでは間に合わないですが、継続して議論が進められているTSもまだまだあり、このあと仕様が固まったものは、C++1zの次のバージョンC++2x (2020年、C++20予定) に入る予定です。そのなかには、コンセプト、モジュールシステム、トランザクショナルメモリ、並行コンテナ、コルーチン、ネットワークなどがあります。

TS以外の機能としても、多くの便利な機能が入ります。構造化束縛やif constexpr文などは、とくに便利ですね。


コア言語

言語機能の更新内容です。

変数・データ構造関係

制御構文

ラムダ式

テンプレート

定数式

名前空間

例外

属性

プリプロセッサ

機能の削除

古くから非推奨だった機能が削除されます。非推奨になった機能は数バージョン先に削除される可能性がありますので、ご注意ください。


ライブラリ

標準ライブラリの更新内容です。

新ライブラリ

コンテナ

文字列

アルゴリズム

並行処理

スマートポインタ

数学

タプル

型特性

時間演算

乱数

エラーハンドリング

取り決め

機能の削除

古くから非推奨だった機能や、コンパイラの実装が不十分でユーザーにも使われてこなかった機能などが削除されます。非推奨になった機能は数バージョン先に削除される可能性がありますので、ご注意ください。

機能の非推奨化


まとめていない主な機能

これのほかにも、まとめていない細かい更新はたくさんあります。詳細な更新リストは、cpprefjp/siteリポジトリの以下のWikiページを参照してください。


お断り

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

C++1z using宣言のパック展開

using宣言 (using-declaration) には、2つの用途があります:

  1. メンバ関数を、基本クラスと派生クラスでオーバーロードする
  2. 識別子の名前空間を省略できるようにする

これらusing宣言に指定する識別子が、ひとつだけでなく、カンマ区切りで複数指定できるようになります。

メンバ関数のusing宣言

C++11で可変引数テンプレートが導入されたことにより、派生クラスを定義する際に、基本クラスのリストを受け取ってまとめて多重継承できるようになりました。その際、using宣言の対象をひとつしか指定できなかったため、基本クラスと派生クラスでメンバ関数オーバーロードをする場合、以下のように、再帰テンプレートによってusing宣言するという回避策をとる必要がありました:

#include <iostream>
#include <iomanip>
#include <utility>

template <typename T, typename... Ts>
struct Overloader : T, Overloader<Ts...> {
    using T::operator();
    using Overloader<Ts...>::operator(); // 回避策:再帰でusing宣言
};

template <typename T> struct Overloader<T> : T {
    using T::operator();
};

template <typename... Ts>
constexpr auto make_overloader(Ts&&... ts)
{
    return Overloader<Ts...>{std::forward<Ts>(ts)...};
}

int main()
{
    auto o = make_overloader([] (auto a) {std::cout << a << std::endl;},
                             [] (float f) {std::cout << std::scientific << f << std::endl;});

    o("hello");
    o(1.2f);
}

C++1zでusing宣言に複数の識別子を指定できるようになることで、これがより簡潔に書けるようになります。

#include <iostream>
#include <iomanip>
#include <utility>

template <typename... Ts>
struct Overloader : Ts... {
    using Ts::operator()...; // C++1z
};

template <typename... Ts>
constexpr auto make_overloader(Ts&&... ts)
{
    return Overloader<Ts...>{std::forward<Ts>(ts)...};
}

int main()
{
    auto o = make_overloader([] (auto a) {std::cout << a << std::endl;},
                             [] (float f) {std::cout << std::scientific << f << std::endl;});

    o("hello");
    o(1.2f);
}

名前空間を省略するためのusing宣言

using宣言のパック展開は、主な目的は先に示したメンバ関数オーバーロードですが、副次的に、名前空間を省略するためのusing宣言も、カンマ区切りで複数指定できるようになります。

#include <iostream>

int main()
{
    using std::cout, std::endl; // C++1z

    // C++14まで
    // using std::cout;
    // using std::endl;

    cout << "hello" << endl;
}

複数をカンマ区切りで指定できるだけなので、using std::cout, endl;のように2つ目以降の指定で名前空間を省略するような書き方はできません。

参照

お断り

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

C++1z shared_ptr::use_count()の仕様を明確化し、unique()を非推奨化

std::shared_ptrクラスのメンバ関数use_count()unique()は参照カウンタがいくつあるか (リソースを共有しているユーザーが何人いるか) を返すもので、デバッグ目的にしか使用しません。また、複数スレッドから使用するには、仕様が不明確でした。

use_count()は引き続きデバッグ目的の機能として残り、複数スレッドからのアクセスについて、仕様が明確化されます。現在の多くの実装が、同期しない読み込み (relaxed load) になっていたこともあり、use_count()は仕様でも「同期しない」ことが明確化されます。そのため、複数スレッドからこのメンバ関数を使用する場合、戻り値で返される参照カウンタは「おおよその値」と見なし、ヒントとして扱うことになります。

unique()メンバ関数の方は非推奨になります。この関数はuse_count() == 1の結果のbool値を返すものです。これが非推奨になるのは、use_count()が複数スレッドからのアクセスに対して同期せず、「おおよその値」しか返さないことで、unique()メンバ関数の戻り値の信頼性が高くないことが理由であると考えられます。

参照

お断り

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

C++1z shared_ptrの配列対応

C++1zでは、unique_ptr<T[ ]>と同様に、shared_ptrもテンプレート引数をshared_ptr<T[ ]>もしくはshared_ptr<T[N]>のように指定することで、配列を扱えるようになります。

std::shared_ptr<double[1024]> p1 {new double[1024]};
std::shared_ptr<double[]> p2 {new double[n]};

// 添字アクセス
double* p = p1[0];

shared_ptr<T[ ]>std::vector<shared_ptr<T>>と比べて、実装として参照カウンタがひとつで済みます。そのため、空間とパフォーマンスはshared_ptr<T[ ]>の方が優れています。

この配列では、以下のような仕様の更新・追加があります:

  • コンストラクタが配列も受け取れるような仕様に文面変更 (インタフェースは変わらない)
  • 要素型を表すメンバ型としてelement_typeを追加
    • テンプレートパラメータTを直接使用していたところを、配列も考慮した要素型element_typeを使用するように変更
  • 配列用にoperator[ ]を追加
    • テンプレートパラメータが配列型でない場合、この演算子が定義されるかは未規定
  • std::reinterpret_pointer_cast()関数を追加
    • 互換性のある配列間の変換で使用する。shared_ptr<T[ ]> p;reinterpret_pointer_cast<U[ ]>(p)shared_ptr<U[ ]>に変換する

標準にshared_ptrが入る以前、Boostにはshared_arrayという配列を扱う専用のスマートポインタもありましたが、標準ではその設計は採用しませんでした。unique_ptrを先に配列に対応したため、shared_ptrはそちらに合わせて配列対応することになりました。

この配列対応は、Boostでは1.53.0から入っています。

make_shared()の配列対応がN3939で提案されていますが、これはC++1zでは採用されていません。Boostでは1.56.0で対応しています。

参照

お断り

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

C++1z Chronoライブラリ durationクラスとtime_pointクラスの変更操作をconstexpr対応

ChronoライブラリはC++11, C++14と段階的にconstexprの対応を進めてきました。C++1zでは、durationの変更操作、time_pointの変更操作が全てconstexpr対応します。

template <class Rep, class Period = ratio<1>>
class duration {
public:
    constexpr duration& operator++();
    constexpr duration operator++(int);
    constexpr duration& operator--();
    constexpr duration operator--(int);

    constexpr duration& operator+=(const duration& d);
    constexpr duration& operator-=(const duration& d);

    constexpr duration& operator*=(const rep& rhs);
    constexpr duration& operator/=(const rep& rhs);
    constexpr duration& operator%=(const rep& rhs);
    constexpr duration& operator%=(const duration& rhs);
};
template <class Clock, class Duration = typename Clock::duration>
class time_point {
public:
    constexpr time_point& operator+=(const duration& d);
    constexpr time_point& operator-=(const duration& d);
};

これで、Chronoライブラリでconstexprになっていないのは、Clockだけになります。標準で定義されるClockは、実行環境の時計を扱うため、constexpr化はできません。自分で定義するClockがコンパイル時に扱えるものであれば、その時間演算は全てconstexprで行えるようになります。

参照

お断り

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

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

参照

編集履歴

  • 2018/03/01 11:51 : コメント欄での指摘を受け、-limits::quiet_NaN()のように負数のNaNを使用していたところの符号を削除

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

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

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

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