Boost 1.65.1リリース

Boost 1.65.0に重大なバグが何件かあったため、パッチバージョンアップしたBoost 1.65.1がリリースされました。1.65.0はスキップして1.65.1を使用してください。

1.65.1のリリースノートには、1.65.0の修正内容も含まれます。

標準C++の欠陥解決は、過去のバージョンに遡って適用される

C++17で入る予定の、可変引数でミューテックスを受け取ってスコープを抜けたらロック解除するscoped_lockクラスですが、C++17がDIS (Draft International Standard) の段階になり、仕様の手直しがもうほぼできない段階になってから、引数順の変更が行われました。

この変更は、ドラフト仕様 (Working Draft) としては、さらに次のバージョンのC++20に適用されました (Editor’s Reportを参照) 。しかし、libstdc++、libc++といった標準ライブラリの実装や、cppreferenceサイトなどでは、この変更がC++17に取り込まれたものとして扱っていました。

さらに、cppreferenceサイトでは、「この変更は、過去にリリースされたC++バージョンに遡って適用された」と書いてありました。そんなことが標準ドキュメントのどこに書いてあるのかわからず、また、過去のバージョンに遡って適用される変更がどれなのかわからないと、なにをC++17と言っていいのかわからなくなります。ですので、std-discussionメーリングリストで質問してみました。

この質問への返答を要約すると、以下のような扱いになっていました:

  • 欠陥 (Defect) の解決は、基本的に過去のバージョンに遡って適用される
  • これは、既知の欠陥と解決策がわかっていれば、コンパイラや標準ライブラリの実装はできるだけ早くそれを適用したいから

それと今回の場合、C++17はまだ策定が完了しておらず、その段階で標準ライブラリの全ての実装が、すでにこの変更に対応しています。ですので、今回の変更は少なくともC++17に含まれると考えられます。また、欠陥の修正は、どのバージョンの仕様書に適用されたかだけでなく、修正内容が報告された時期が重要で、実装にはすぐに適用されることがある、ということがわかりました。

インクルードするディレクトリをマクロ定数として持つ

とある事情から「インクルードするディレクトリが長いので何度も書きたくない」という状況になり、インクルードするディレクトリをマクロ定数に持って、インクルードするファイル名と連結してインクルードしたい、ということがありました。

そんなときの対応は、このようになりました:

// BOOST_PP_STRINGIZEと同等
// トークンを文字列化する
#define PP_XSTR(M) #M
#define PP_STR(x) PP_XSTR(x)

// インクルードするディレクトリ。
// 最後が / になってはならない (トークンとして使えない文字、というエラーになる)
#define MY_LIB_PATH aaa/bbb/ccc

// aaa/bbb/ccc/ddd.hをインクルードする
#include PP_STR(MY_LIB_PATH/ddd.h)

int main() {}

https://wandbox.org/permlink/I6jGIJcqLE4RMHFE

参照

C++17標準ライブラリの細かい変更いろいろ その1

最近cpprefjpに書いたものを列挙します。

  • assertマクロがconstexpr関数内で使用できるようになった
  • std::next()関数のイテレータ要件がForward IteratorからInput Iteratorに緩和された
  • std::addressof()const T&&の引数を禁止にした。addressof<const T>(T())のようにするとconst T&&が指定できていた
  • std::addressof()がconstexprに対応した
  • std::mutexとかstd::recursive_mutexlock()メンバ関数が、device_or_resource_busyのエラーを起こらなくした
  • 非順序連想コンテナのreserve()メンバ関数が、C++14までn-1以上の予約していたが、C++17からn以上が予約されるようになった
  • std::shared_ptr<const void>のようなテンプレート引数に対して間接参照演算子が適用できるようになった
  • タプルのstd::get()関数に、const T&&版を追加。const付きで戻り値を返すと、その一時オブジェクトはconst T&&になっていたため
  • std::atomicクラスのテンプレート引数設定済みの別名として、atomic_uint8_tのような固定幅整数型を追加
  • std::atomicクラスvalue_typedifference_typeを追加。fetch系の関数Tではなくdifference_typeを受け取るよう変更された
  • std::numeric_limits::is_moduloの符号あり整数型に対する仕様が変更された。これまでは符号あり整数型がオーバーフローしたときの未定義動作としての最小値に戻る挙動をtrueとして扱っていたのをやめた。規格上は符号あり整数型はfalseで、処理系がオーバーフローしたときの動作をラップして最小値に戻る挙動として定義したときにtrueとなるようにした
  • std::vectorstd::dequestd::basic_stringshrink_to_fit()メンバ関数で、メモリを縮小して再割り当てが発生するときに、イテレータの無効化が起こることが明記された。明記されただけで、元々起こっていただろう
  • std::basic_stringのコンストラクタに、basic_string(const basic_string& str, size_type pos, const Allocator& a = Allocator());オーバーロードを追加

標準ライブラリの実装で追加のnoexceptが付いている場合がある

標準ライブラリの仕様でnoexceptが付いているものは、実装にもnoexceptを付けることが求められます。

しかし、標準ライブラリの仕様でnoexceptが付いていない場合、実装にnoexceptを付けないことは求められません。

たとえば、libc++のstd::vectorクラスでは、以下のメンバ関数noexceptが付いています。

// https://github.com/llvm-mirror/libcxx/blob/018a3d51a47f7275c59e802709104498b729522b/include/vector

vector()
    noexcept(is_nothrow_default_constructible<allocator_type>::value);

vector(vector&& x)
    noexcept(is_nothrow_move_constructible<allocator_type>::value);

vector& operator=(vector&& x)
    noexcept(
         allocator_type::propagate_on_container_move_assignment::value ||
         allocator_type::is_always_equal::value);

void shrink_to_fit() noexcept;

void swap(vector&)
    noexcept(allocator_traits<allocator_type>::propagate_on_container_swap::value ||
             allocator_traits<allocator_type>::is_always_equal::value);

libstdc++のstd::vectorクラスでは、以下のメンバ関数noexceptが付いています。

// https://github.com/gcc-mirror/gcc/blob/67886b40399e79bcc392a152e96d9116ba24e2ed/libstdc%2B%2B-v3/include/bits/stl_vector.h

vector()
    noexcept(is_nothrow_default_constructible<_Alloc>::value);

vector(vector&& __x) noexcept;

vector(vector&& __rv, const allocator_type& __m)
    noexcept(_Alloc_traits::_S_always_equal());

vector&
    operator=(vector&& __x) noexcept(_Alloc_traits::_S_nothrow_move());

// swapで例外を投げそうなアロケータはコンパイルエラー
void swap(vector& __x) _GLIBCXX_NOEXCEPT;

これにより、特定の標準ライブラリを使用する場合に、例外を投げない強い保証をするコードを書けることがあります (とくにデフォルトコンストラクタが有用)。また、仕様でnoexceptが付いていない関数に対して、noexcept(式)で例外を投げないかチェックすることにも意義があります。

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

Boost 1.64.0がリリースされました。リリースノートを日本語翻訳したものは、いつものようにboostjpサイトで公開しています。

新ライブラリ

主な更新

今回はほとんどがバグ修正になっています。

Visual Studio 2017 (14.1)がサポートされています。

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の以下の階層の下に解説ページを用意する予定です。