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
が足されました。
size_t hash_code() const; // C++11 size_t hash_code() const noexcept; // C++14
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::async
かstd::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
無事に動いた。
確認した環境:
イテレータの受け取り方
以下のコードは、コンパイルエラーになる:
template <class Iterator> Iterator f(const Iterator& it) { return it; } int main() { int ar[] = {1, 2, 3}; f(ar); // コンパイルエラー!呼び出し可能なf()のオーバーロードがない }
関数f()
はイテレータをconst
左辺値参照で受け取る。このようにした場合、テンプレートパラメータIterator
はint[3]
に推論される。C++の言語仕様として、組み込み配列はreturn
文で返せないので、コンパイルエラーになる(実際には、戻り値型のところでSFINAEが起きる)。
以下のように、イテレータをコピーで受け取ることで関数f()
を呼び出せるようになる:
template <class Iterator> Iterator f(Iterator it) { return it; } int main() { int ar[] = {1, 2, 3}; f(ar); // OK }
こうすることで、テンプレートパラメータIterator
はint*
に推論される。ポインタはreturn
文で返せるので、これは問題なく通る。
この変更が、C++14でのmake_move_iterator()
関数の仕様に適用された。
ちなみに、この推論ルールは、decay
(ディケイと読む)という名前で知られており、<type_traits>
ライブラリにはその推論ルールを適用するメタ関数が定義されている。
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); }
関数の型F
、F1
、F2
は、以下のいずれかのシグニチャを持たせます:
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
オブジェクトが有効な状態かどうか判定してから中身を取り出す」という二段階の処理を同時に行い、「じつは無効な状態なのに中身を取り出そうとした」というミスを防ぐために使用します。
expected
のmap()
メンバ関数のようなものです。
実装技術の話
関数から引数の型を取り出して「有効な値用の関数か、無効の値用の関数か」を判定するのではなく、関数が特定のシグニチャで呼び出し可能かを調べるis_callable
メタ関数を使用して、判定しています。
is_callable<F, T&>::value == true
(F
がT&
型を受け取って呼び出し可能な関数か、が真)なら有効な値用の関数と見なしています。
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::vector
やstd::deque
はstd
名前空間で定義されるのでADLでbegin()
/end()
を呼び出せるが、配列は名前空間に属さないので、ADLで呼び出せない。そのため、配列版のオーバーロードが定義されるstd
名前空間だけは、using宣言する必要がある。
隣接要素を処理するfor_each
稀によく使うので書いた。const
版のみ実装した。
実装:
template <class InputRange, class BinaryFunction> void adjacent_for_each(const InputRange& range, BinaryFunction f) { // for ADL using std::begin; using std::end; auto first = begin(range); auto last = end(range); if (first == last) return; while (std::next(first) != last) { const auto& a = *first; ++first; const auto& b = *first; f(a, b); } }
サンプルコード:
#include <iostream> #include <vector> int main() { const std::vector<int> v = {1, 2, 3}; adjacent_for_each(v, [](int a, int b) { std::cout << a << " : " << b << std::endl; }); }
出力:
1 : 2 2 : 3
参照
shared_ptrをweak_ptrに変換するヘルパ関数
// shand/to_weak.hpp #include <boost/config.hpp> #include <memory> namespace shand { template <class T> std::weak_ptr<T> to_weak(const std::shared_ptr<T>& sp) BOOST_NOEXCEPT { return sp; }
C++14から入った、ラムダ式のキャプチャでの初期化で使用する。
#include <iostream> #include <memory> #include <shand/to_weak.hpp> int main() { std::shared_ptr<int> sp(new int(3)); auto f = [wp = shand::to_weak(sp)] { // これ if (std::shared_ptr<int> sp = wp.lock()) { std::cout << *sp << std::endl; } }; f(); }
出力:
3