読者です 読者をやめる 読者になる 読者になる

明日はBoost.勉強会 東京です

C++

明日はBoost.勉強会です。いつものように、IIJ様の会場をお借りして開催します。

私はBoost 1.61.0のアップデート内容と、C++1z (C++17になる予定のバージョン)の全容を解説します。

発表資料は、boostjpサイトの以下のページで公開する予定です。

C++1z not_fn

C++

これまで、述語(predicate)を反転させるためには、std::not1()std::not2()という関数がありました。

これらの関数はそれぞれ1引数の述語関数オブジェクト、2引数の述語関数オブジェクトを対象としますが、C++11で導入された可変引数テンプレートがあれば、任意の引数を受け取る述語を扱えます。そのため、新たにstd::not_fn()という関数が定義されるようになります。

// <functional>
namespace std {
  template <class F>
  unspecified not_fn(F&& f);
}

この関数には、可変引数テンプレートの制限に引っかかることのない限り、任意の数の引数を受け取る述語関数オブジェクトを指定できます。

#include <cassert>
#include <functional>

int main()
{
    assert(std::not_fn([] { return true; })() == false);
    assert(std::not_fn([](int) { return true; })(1) == false);
    assert(std::not_fn([](int, int) { return true; })(1, 2) == false);
    assert(std::not_fn([](int, int, int) { return true; })(1, 2, 3) == false);
}

std::not()という関数名にしないのは、not予約語だからです。

std::not_fn()関数の追加にともない、以下の機能は非推奨となります:

  • std::not1()
  • std::not2()
  • std::unary_negate
  • std::binary_nagate
  • 標準関数オブジェクトのresult_typeargument_typefirst_argument_typesecond_argument_type

参照

お断り

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

C++1z basic_string::data()メンバ関数の非const版

C++

basic_stringクラスが保持している動的文字配列の生ポインタを取得するdata()メンバ関数ですが、C++14まではconstメンバ関数のみ用意されていました。

C++1zでは、非const版のdata()メンバ関数が追加されます。これにより、char*を要求するインタフェースに対してstd::stringオブジェクトを使いやすくなります。

たとえば、なぜかchar*を要求するWindowsCreateProcess()関数のコマンドライン引数には、

BOOL CreateProcess(
  LPCTSTR lpApplicationName,                 // 実行可能モジュールの名前
  LPTSTR lpCommandLine,                      // コマンドラインの文字列 ←これ
  LPSECURITY_ATTRIBUTES lpProcessAttributes, // セキュリティ記述子
  LPSECURITY_ATTRIBUTES lpThreadAttributes,  // セキュリティ記述子
  BOOL bInheritHandles,                      // ハンドルの継承オプション
  DWORD dwCreationFlags,                     // 作成のフラグ
  LPVOID lpEnvironment,                      // 新しい環境ブロック
  LPCTSTR lpCurrentDirectory,                // カレントディレクトリの名前
  LPSTARTUPINFO lpStartupInfo,               // スタートアップ情報
  LPPROCESS_INFORMATION lpProcessInformation // プロセス情報
);

C++1zでは以下のようにして引数を渡せます:

std::string applicationName;
std::string commandLineArgs;

// ...

if (CreateProcess(applicationName.data(), commandLineArgs.data(), /* etc. */)) {
  // etc.
}
else {
  // handle error
}

参照

お断り

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

C++1z emplace_frontとemplace_backで追加された要素を返す

C++

シーケンスコンテナのemplace_front()メンバ関数emplace_back()メンバ関数は、C++14では戻り値型がvoidでしたが、C++1zでは追加された要素への参照が返るようになります。

これは、追加した要素をすぐ使いたい場合に、以下のような冗長なコードになっていた問題への対処です:

// case 1
my_container.emplace_back(…);
my_container.back().do_something(…);

// case 2
my_container.emplace_back(…);
do_something_else(my_container.back());

C++1zでは、このような状況で以下のように書けるようになります:

// case 1
my_container.emplace_back(…).do_something(…);

// case 2
do_something_else(my_container.emplace_back(…));

戻り値の型の変更は、ABIを破壊せず、パフォーマンスにも影響はありません。

変更対象は、シーケンスコンテナのemplace_front()emplace_back()、およびqueueクラスとstackクラスのemplace()メンバ関数になります。

push_front()push_back()は変更なしで、戻り値の型はこれまで通りvoidです。

参照

お断り

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

C++1z owner_lessで任意の要素型を持つshared_ptr同士を比較できるようにする

C++

所有権ベースの小なり比較を行うstd::owner_less関数オブジェクトですが、そのクラステンプレートのパラメータによってスマートポインタの要素型を制約してしまうため、以下のような比較ができませんでした。

shared_ptr<int> sp1;
shared_ptr<void> sp2;
shared_ptr<long> sp3;
weak_ptr<int> wp1;

owner_less<shared_ptr<int>> cmp;
cmp(sp1, sp2); // コンパイルエラー
cmp(sp1, wp1);
cmp(sp1, sp3); // コンパイルエラー
cmp(wp1, sp1);
cmp(wp1, wp1); // コンパイルエラー

C++1zでは、std::owner_lessクラステンプレートにvoidの特殊化版が定義され、そのバージョンでは関数テンプレートの関数呼び出し演算子を持つようになります:

// デフォルトテンプレート引数がvoidになり、
template<class T = void> struct owner_less;

// voidに対する特殊化が追加
template<> struct owner_less<void> {
  template<class T, class U>
    bool operator()(shared_ptr<T> const&, shared_ptr<U> const&) const;
  template<class T, class U>
    bool operator()(shared_ptr<T> const&, weak_ptr<U> const&) const;
  template<class T, class U>
    bool operator()(weak_ptr<T> const&, shared_ptr<U> const&) const;
  template<class T, class U>
    bool operator()(weak_ptr<T> const&, weak_ptr<U> const&) const;

  typedef unspecified is_transparent;
};

これによって、異なる要素型を持つスマートポインタや、shared_ptr<void>のような特殊な要素型を持つスマートポインタとの比較ができるようになります。

参照

お断り

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

C++1z 時間の丸め演算

C++

時間ユーティリティライブラリの<chrono>には、精度が下がる単位変換をする場合に使用するduration_cast()関数があります。

C++1zでは、このときの丸め演算をユーザーが選択するための機能が導入されます。

まず、整数を時間間隔の値型とするdurationのための、floor() (切り下げ)、ceil() (切り上げ)、round() (最近接偶数への丸め)。それと符号あり整数の時間間隔を持つdurationのためのabs() (絶対値)。

#include <iostream>
#include <chrono>

using namespace std::chrono;

int main()
{
    milliseconds ms(2500);
    seconds sec = round<seconds>(ms); // 500は精度落ちする

    std::cout << sec.count() << std::endl; // 2
}

それと、そのdurationを持つtime_pointのための、floor()ceil()round()が入ります。

Boost 1.61.0時点のBoost.Chronoライブラリには、durationに対するfloor()ceil()round()があります。

参照

お断り

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

C++1z hypot関数の3引数版

C++

C++11で<cmath>に追加されたhypot()関数ですが、2引数(2次元)の演算のみが用意されていました。

C++1zでは、3引数版が追加されます。動機としては、科学計算や工学アプリケーションで3次元の演算をすることが多いためだそうです。

// <cmath>
namespace std {
  double hypot(double x, double y, double z);
  float hypot(float x, float y, float z);
  long double hypot(long double x, long double y, long double z);
}

この関数の効果:

{ \displaystyle
 \sqrt{x^{2} + y^{2} + z^{2}}
}

参照

お断り

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

C++1z 数学の特殊関数

C++

C++1zでは、C++11のころから繰り越されていた数学の特殊関数(special functions)が導入されます。

一覧としては、以下になります:

関数名 説明
assoc_laguerre ラゲールの陪多項式
associated Laguerre polynomials
assoc_legendre ルジャンドルの陪多項式
associated Legendre polynomials
beta ベータ関数
beta function
comp_ellint_1 (完全)第1種楕円積分
(complete) elliptic integral of the first kind
comp_ellint_2 (完全)第2種楕円積分
(complete) elliptic integral of the second kind
comp_ellint_3 (完全)第3種楕円積分
(complete) elliptic integral of the third kind
cyl_bessel_j (第1種)円筒ベッセル関数
cylindrical Bessel functions (of the first kind)
cyl_bessel_i 正規変形円筒ベッセル関数
regular modified cylindrical Bessel functions
cyl_bessel_k 非正規変形円筒ベッセル関数
irregular modified cylindrical Bessel functions
cyl_neumann 円筒ノイマン関数 (第2種円筒ベッセル関数)
cylindrical Neumann functions, cylindrical Bessel functions (of the second kind)
ellint_1 (不完全)第1種楕円積分
(incomplete) elliptic integral of the first kind
ellint_2 (不完全)第2種楕円積分
(incomplete) elliptic integral of the second kind
ellint_3 (不完全)第3種楕円積分
(incomplete) elliptic integral of the third kind
expint 指数積分
exponential integral
hermite エルミート多項式
Hermite polynomials
legendre ラゲールの多項式
Laguerre polynomials
laguerre ルジャンドル多項式
Legendre polynomials
riemann_zeta リーマンゼータ関数
Riemann zeta function
sph_bessel (第1種)球ベッセル関数
spherical Bessel functions (of the first kind)
sph_legendre 球ラゲール陪多項式
spherical associated Legendre functions
sph_neumann ノイマン関数 (第2種球ベッセル関数)
spherical Neumann functions, spherical Bessel functions (of the second kind)

これらの関数は、<cmath>ヘッダに追加されます。C言語の標準ライブラリにこれらの関数はないので、Cへの参照ではなく、C++の標準ライブラリとして動作が定義されます。

これらのほかに、float版として末尾にfがついたバージョン、long double版として末尾にlがついたバージョンが定義されます。

参照

お断り

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

C++1z false sharingとtrue sharingの制御

C++

C++1zから、並行プログラミングで問題になりうる、キャッシュの無効化問題を制御できるようになります。

false sharingの制御

struct keep_apart {
    atomic<int> cat;
    atomic<int> dog;
};

このような構造体がある場合、catdogが同じキャッシュラインに乗ることがあります。スレッド1ではcat変数、スレッド2ではdog変数を操作するような状況で、それぞれが共通のキャッシュを無効化してしまうパフォーマンス劣化の問題が起こりえます。こういった状況をfalse sharingと言いますが、これを回避するために、<new>ヘッダにstd::hardware_destructive_interference_sizeという定数が定義されます。(「破壊的な干渉サイズ」といったところでしょうか)

この定数を構造体メンバのアライメントとして指定することによって、それぞれのメンバが独立したキャッシュラインに乗り、false sharingが回避できるようになります。

struct keep_apart {
    alignas(hardware_destructive_interference_size) atomic<int> cat;
    alignas(hardware_destructive_interference_size) atomic<int> dog;
};

true sharingの制御

true sharingという用語は一般的ではないようですが、意図的に複数のメンバを同一のキャッシュラインに乗せることがそう呼ばれます。

そのようなことをするために、同ヘッダにstd::hardware_constructive_interference_sizeという定数が定義されます。構造体全体に対してこの定数をアライメント指定することによって、構造体全体が同一キャッシュラインに乗ることが期待できるようになります。

struct alignas(hardware_constructive_interference_size) together {
    atomic<int> dog;
    int puppy;
};

宣言

// <new>
namespace std {
  static constexpr size_t hardware_destructive_interference_size = 実装定義;
  static constexpr size_t hardware_constructive_interference_size = 実装定義;
}

備考

私は並行プログラミングの専門家ではないこともあって、使用している用語や説明に間違いがあるかもしれません。そういったことがあったらご指摘いただけると助かります。

参照

お断り

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

C++1z 値を範囲内に収めるclamp()関数

C++

値を、min <= v <= maxのように範囲内に収める関数として、clamp()が定義されます。

std::min(std::max(min_value, x), max_value)のようにするのと同等の効果があります。

引数の順番は、対象の値、最小値、最大値です。戻り値として範囲内に丸められた値が返ります。

// <algorithm>
namespace std {
  template <class T>
  constexpr const T& clamp(const T& v, const T& lo, const T& hi);

  template <class T, class Compare>
  constexpr const T& clamp(const T& v, const T& lo, const T& hi, Compare comp);
}

例:

#include <iostream>
#include <algorithm>

int main()
{
    int v = 3;

    // vの値を[0, 2]の範囲内に収める
    int result = std::clamp(v, 0, 2);
    std::cout << result << std::endl;
}

出力:

2

参照

お断り

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