C++14標準ライブラリの小さな変更 その3

その1その2に引き続き、C++14の標準ライブラリに入った小さな変更を紹介していきます。

細かいnoexceptの付け忘れ対応

C++11で<memory>ヘッダに追加されたアロケータの中間インタフェースstd::allocator_traitsクラスmax_size()静的メンバ関数に、noexceptが追加されました。

static size_type max_size(Alloc& a);                // C++11
static size_type max_size(const Alloc& a) noexcept; // C++14

ついでに、constの付け忘れにも対応。

weak_ptrのlockがスレッドセーフに

weak_ptrオブジェクトが監視しているshared_ptrオブジェクトを取得するlock()メンバ関数が、アトミックに実行されることが保証されました。

shared_ptr<T> lock() const noexcept;

戻り値:
expired() ? shared_ptr<T>() : shared_ptr<T>(*this)

監視しているshared_ptrオブジェクトが有効な状態なら、そのshared_ptrオブジェクトとリソースを共有するshared_ptrオブジェクトを作って返す。これによって、ロックしている間、shared_ptrオブジェクトの寿命が尽きないようにする。

監視しているshared_ptrオブジェクトが寿命切れ状態なら、空のshared_ptrオブジェクトを作って返す。

C++14 : 上記に相当することをアトミックに実行する。

文字列から数値に変換する関数のエラーに、ERANGEを考慮する

C++11からされたbasic_stringを整数もしくは浮動小数点数に変換する関数に、ERANGEエラーが考慮されるようになりました。

変更対象となる関数は以下です:

C++11では、「std::strtol()などの関数を使用した変換結果の値が、結果型で表現可能な範囲を超えていたらエラー」という仕様はありました。

C++14にはこれに加えて、「変換に使用する内部関数std::strtol()が範囲外エラーになったらエラーとする」という仕様が追加されました。

今日はこれまで

まだ続きます。

C++14標準ライブラリの小さな変更 その2

その1に引き続き、C++14の標準ライブラリに入った、小さな変更を紹介していきます。

weak_ptrがムーブに対応した

C++14では、弱参照スマートポインタのstd::weak_ptrクラスに、以下のコンストラクタと、

weak_ptr(weak_ptr&& r) noexcept;

template <class Y>
weak_ptr(weak_ptr<Y>&& r) noexcept;

以下の代入演算子が追加されました。

weak_ptr& operator=(weak_ptr&& r) noexcept;

template <class Y>
weak_ptr& operator=(weak_ptr<Y>&& r) noexcept;

これまで、weak_ptrはコピーはできたので、既存コードに影響はありません。

細かいnoexceptの付け忘れ対応

constexpr operator value_type()          { return value; } // C++11
constexpr operator value_type() noexcept { return value; } // C++14
  • <ios>ヘッダの以下の関数に、noexceptが追加されました。

iostream_category()関数 : 入出力のエラーカテゴリオブジェクトを作って返す。

namespace std {
  const error_category& iostream_category();          // C++11
  const error_category& iostream_category() noexcept; // C++14
}

make_error_code()関数 : 入出力関係のエラーコードを作って返す。

namespace std {
  error_code make_error_code(io_errc e);          // C++11
  error_code make_error_code(io_errc e) noexcept; // C++14
}

make_error_condition()関数 : 入出力関係のエラーコードに紐づく情報を、作って返す。

namespace std {
  error_condition make_error_condition(io_errc e);          // C++11
  error_condition make_error_condition(io_errc e) noexcept; // C++14
}

メンバ関数版の組み込み配列用begin()end()に、noexceptが追加されました。

namespace std {
  template <class T, size_t N>
  T* begin(T (&array)[N]);                    // C++11

  template <class T, size_t N>
  constexpr T* begin(T (&array)[N]) noexcept; // C++14
}
namespace std {
  template <class T, size_t N>
  T* end(T (&array)[N]);                    // C++11

  template <class T, size_t N>
  constexpr T* end(T (&array)[N]) noexcept; // C++14
}

future_errcの開始値が0ではなくなった

C++11で<future>ヘッダに追加された、futurepromise関係のエラー値列挙型std::future_errcですが、列挙値が実装定義になりました。

C++11:

namespace std {
  // C++11
  enum class future_errc {
    broken_promise,
    future_already_retrieved,
    promise_already_satisfied,
    no_state
  };
}

C++14:

namespace std {
  enum class future_errc {
    broken_promise = implementation-defined,
    future_already_retrieved = implementation-defined,
    promise_already_satisfied = implementation-defined,
    no_state = implementation-defined
  };
}

この変更は、std::future_errcの値をstd::error_codeオブジェクトに入れたときに、開始値が0だと困るからです。

始値broken_promiseは、正常値ではなくエラー値です。これが0だと困るのは、std::error_codeクラスのoperator bool()が、以下の仕様になっていることです。

explicit operator bool() const noexcept;

戻り値:
return value() != 0

つまり、0を正常値、0以外をエラー値としています。

そのため、C++11のbroken_promisestd::error_codeに設定すると、エラー値なのに正常値と見なされてしまいます。

C++14のこの変更で、std::future_errcのいずれの値も0以外になります。

今日はこれまで

さらに続きます。

C++14標準ライブラリの小さな変更 その1

C++日本語リファレンスとサイトcpprefjpで、C++14対応を進めているので、このブログですでに紹介した大きな機能以外の、小さな変更点を紹介していきます。

コンテナに挿入するイテレータアダプタに、addressofを使うようになった

C++11から、std::addressof()という関数が<memory>ヘッダに追加されました。

この関数は、オブジェクトのアドレスを取得するoperator&()オーバーロードされていたとしても、そのオブジェクトのアドレスを取得できるようにする関数です。Boostから導入されたもの。

標準ライブラリのテンプレート内で、オブジェクトのポインタを取得する箇所は、このaddressof()関数を使用するようになりました。

back_insert_iteratorコンストラクタは、以下のように仕様が変わっています。

explicit back_insert_iterator(Container& x);

xへのポインタをメンバ変数containerに保持する。

C++11まで : xへのポインタは、&xで取得する
C++14以降 : xへのポインタは、std::addressof(x)で取得する

細かいnoexceptの付け忘れ対応

std::type_indexクラスの以下のメンバ関数に、付け忘れていたnoexceptが足されました。

hash_code()メンバ関数

size_t hash_code() const;          // C++11
size_t hash_code() const noexcept; // C++14

name()メンバ関数

const char* name() const;          // C++11
const char* name() const noexcept; // C++14

ほかにもいろいろあるので、追々紹介していきます。

async関数に、不正なポリシーが指定された場合の挙動が追加された

namespace std {
  template <class F, class... Args>
  future<typename result_of<F(Args...)>::type>
    async(launch policy, F&& f, Args&&... args);
}

C++11から<future>ヘッダに追加された、簡単な非同期処理をするためのasync()関数には、std::launch::asyncstd::launch::deferredの、いずれかの実行ポリシーを指定します。

この実行ポリシーは列挙型の値なので、通常はこのどちらかしか渡されないのですが、整数値を無理やりstd::launch型にキャストしてstd::async()関数に指定される可能性があります。

C++14では、そのような値が渡された場合、「未定義動作になる」ということが規定されました。これまで未規定だったので、たいして変わらないです。

今日はこれまで

続きます。

Boost.Fusionのアルゴリズムとジェネリックラムダ

C++14で導入されたジェネリックラムダはBoost.Fusionのためにあるようなものですが、この組み合わせをまだ動かしていなかったので、検証。

#include <iostream>
#include <boost/fusion/include/make_vector.hpp>
#include <boost/fusion/include/for_each.hpp>

int main()
{
    namespace fusion = boost::fusion;
    fusion::for_each(fusion::make_vector(1, "hello", 3.14), [](const auto& x) {
        std::cout << x << std::endl;
    });
}

出力:

1
hello
3.14

無事に動いた。

確認した環境:

  • Boost 1.57.0
  • GCC 4.9 (-std=c++1yオプション, 5.0からは-std=c++14オプション)
  • Clang 3.4 (-std=c++1yオプション, 3.5からは-std=c++14オプション)

イテレータの受け取り方

以下のコードは、コンパイルエラーになる:

template <class Iterator>
Iterator f(const Iterator& it)
{
    return it;
}

int main()
{
    int ar[] = {1, 2, 3};
    f(ar); // コンパイルエラー!呼び出し可能なf()のオーバーロードがない
}

関数f()イテレータconst左辺値参照で受け取る。このようにした場合、テンプレートパラメータIteratorint[3]に推論される。C++の言語仕様として、組み込み配列はreturn文で返せないので、コンパイルエラーになる(実際には、戻り値型のところでSFINAEが起きる)。

以下のように、イテレータをコピーで受け取ることで関数f()を呼び出せるようになる:

template <class Iterator>
Iterator f(Iterator it)
{
    return it;
}

int main()
{
    int ar[] = {1, 2, 3};
    f(ar); // OK
}

こうすることで、テンプレートパラメータIteratorint*に推論される。ポインタはreturn文で返せるので、これは問題なく通る。

この変更が、C++14でのmake_move_iterator()関数の仕様に適用された。

ちなみに、この推論ルールは、decay(ディケイと読む)という名前で知られており、<type_traits>ライブラリにはその推論ルールを適用するメタ関数が定義されている。

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

C++のリファレンスサイトcpprefjpでは、C++14への対応も進めています。

今回、C++14での大きなライブラリ追加として、<shared_mutex>ヘッダのリファレンスを作成しました。

cpprefjpサイトのC++14対応状況は、GitHubリポジトリの以下のWikiにまとめてあります:

編集への参加は、常時受け付けています!

shared_timed_mutexで並行キューを実装した

Readers Writerロックで、並行キューを実装した。

値を返し、例外を投げないpop()も実装しておいた。

実装: shand/concurrent/queue.hpp

インタフェース

namespace shand {
 
template <class T> // Tは、例外を投げないムーブコンストラクタを持っていること
class concurrent_queue {
public:
    concurrent_queue() {}
 
    concurrent_queue(const concurrent_queue&) = delete;
    concurrent_queue& operator=(const concurrent_queue&) = delete;
 
    // write access
    void push(const T& x);
    void push(T&& x);
    boost::optional<T> pop() noexcept;
    void clear();
 
    // read access
    std::size_t size() const;
    bool empty() const;
};
 
} // namespace shand

サンプルコード

#include <iostream>
#include <thread>
#include <shand/concurrent/queue.hpp>
 
void producer(shand::concurrent_queue<int>& que)
{
    for (int i = 0; i < 100; ++i) {
        que.push(i);
    }
}
 
void consumer(shand::concurrent_queue<int>& que)
{
    int i = 0;
    for (;;) {
        if (boost::optional<int> x = que.pop()) {
            std::cout << x.get() << std::endl;
            ++i;
        }
 
        if (i > 30) // てきとうなところで終了する
            return;
    }
}
 
int main()
{
    shand::concurrent_queue<int> que;
 
    std::thread t1(producer, std::ref(que));
    std::thread t2(consumer, std::ref(que));
 
    t1.join();
    t2.join();
}

出力:

0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

値を返し、例外を投げないpop()の実装

値を返し、例外を投げないpop()は、以下のようにして実装しています。

  • 戻り値は、コピーではなくムーブで返す
  • 空の場合を考慮して、optionalで返す
  • クラスのテンプレートパラメータは、例外を投げないムーブコンストラクタを持っている型のみを受け付ける
  • optionalのムーブコンストラクタは、Tのムーブが例外を投げなければ、例外を投げない(と、1.57.0のドキュメントに書いていた)

テンプレートパラメータの要件部分:

template <class T>
class concurrent_queue {
    static_assert(
        std::is_nothrow_move_constructible<T>::value,
        "Type T must be nothrow move constructible");
    …
};

pop()の実装部分:

// que_はstd::deque<T>
boost::optional<T> pop() noexcept
{
    std::lock_guard<std::shared_timed_mutex> lock(mutex_);
    if (que_.empty())
        return boost::none;

    T front = std::move(que_.front());
    que_.pop_front();
    return front;
}

参照

boost::optionalのパターンマッチ as 文

boost::optionalを0 or 1要素のRangeと見なす。

#include <memory>
#include <boost/optional.hpp>

namespace boost {
    template <class T>
    T* begin(boost::optional<T>& opt) noexcept
    {
        return opt.is_initialized() ?
                    std::addressof(opt.get()) :
                    nullptr;
    }

    template <class T>
    T* end(boost::optional<T>& opt) noexcept
    {
        return opt.is_initialized() ?
                    std::addressof(opt.get()) + 1 :
                    nullptr;
    }
}

#include <iostream>
int main()
{
    boost::optional<int> opt = 3;

    for (int& x : opt) {
        std::cout << x << std::endl;
    }
}

出力:

3

ただし、elseが書けない。

boost::optionalをパターンマッチする関数

Vicenteさんが標準向けに提案していた関数を元に、boost::optional用のパターンマッチ関数を作ってみました。

インタフェース:

// shand/match.hpp

namespace shand {

// 1引数版
template <class T, class F>
void match(boost::optional<T>& x, F f);

template <class T, class F>
void match(const boost::optional<T>& x, F f);

// 2引数版
template <class T, class F1, class F2>
void match(boost::optional<T>& x, F1 f1, F2 f2);

template <class T, class F1, class F2>
void match(const boost::optional<T>& x, F1 f1, F2 f2);

}

関数の型FF1F2は、以下のいずれかのシグニチャを持たせます:

  • R(T&) : optionalオブジェクトが有効な値を持っていれば呼ばれる
  • R() : optionalオブジェクトが無効な値なら呼ばれる

使用例:

#include <iostream>
#include <boost/optional.hpp>
#include <shand/match.hpp>

int main()
{
    boost::optional<int> a = 3;
    shand::match(a, [](int& x) { // 有効な値を持っていれば呼ばれる
        std::cout << x << std::endl;
    });
 
    boost::optional<int> b;
    shand::match(b, [] { // 無効な値なら呼ばれる
        std::cout << "none" << std::endl;
    });
 
    shand::match(a, // 有効な場合と無効な場合で関数を呼び分ける
        [](int& x) { std::cout << x << std::endl; },
        [] { std::cout << "none" << std::endl; }
    );
}

出力:

3
none
3

この関数は、「optionalオブジェクトが有効な状態かどうか判定してから中身を取り出す」という二段階の処理を同時に行い、「じつは無効な状態なのに中身を取り出そうとした」というミスを防ぐために使用します。

expectedmap()メンバ関数のようなものです。

実装技術の話

関数から引数の型を取り出して「有効な値用の関数か、無効の値用の関数か」を判定するのではなく、関数が特定のシグニチャで呼び出し可能かを調べるis_callableメタ関数を使用して、判定しています。

is_callable<F, T&>::value == true(FT&型を受け取って呼び出し可能な関数か、が真)なら有効な値用の関数と見なしています。

is_callable<F>::value == true(引数なし)で無効な値用の関数と見なしています。

参照

std::beginとstd::endの使い方

コンテナからイテレータを取り出すstd::begin()関数とstd::end()関数は、テンプレート外では名前空間修飾を付けて呼び出す使い方でいいが、テンプレート内で使用する場合は、using宣言した上で名前空間修飾なしに呼び出す必要がある。(std::swap()と同じ)

#include <iterator>

template <class Range>
void f(const Range& r)
{
    using std::begin;
    using std::end;

    auto first = begin(r);
    auto last = end(r);

    // use first, last...
}

#include <vector>
int main()
{
    std::vector<int> v = {1, 2, 3};
    int ar[] = {4, 5, 6};

    f(v);
    f(ar);
}

これは、標準外で定義されるbegin()end()関数を考慮するため。

標準外では、begin()end()メンバ関数を持っていないコンテナのための非メンバ関数begin()end()関数が定義される。そのため、これらの非メンバ関数は、名前空間修飾をせず、ADLで呼び出す。

std名前空間begin()end()をusing宣言しているのは、配列版を考慮するため。std::vectorstd::dequestd名前空間で定義されるのでADLでbegin()end()を呼び出せるが、配列は名前空間に属さないので、ADLで呼び出せない。そのため、配列版のオーバーロードが定義されるstd名前空間だけは、using宣言する必要がある。