Boost 1.63.0がリリースされました。リリースノートはいつものように、翻訳・情報補完したものをboostjpサイトで公開しています。
このバージョンは、まだリリースされていないVisual C++ 2017には対応していませんのでご注意ください。
前バージョンである1.62.0のリリースが遅れたこともあり、1.63.0がリリースされるまでの期間は短めになっていました。そのため、小規模な修正版になっています。
Boost 1.63.0がリリースされました。リリースノートはいつものように、翻訳・情報補完したものをboostjpサイトで公開しています。
このバージョンは、まだリリースされていないVisual C++ 2017には対応していませんのでご注意ください。
前バージョンである1.62.0のリリースが遅れたこともあり、1.63.0がリリースされるまでの期間は短めになっていました。そのため、小規模な修正版になっています。
C++11から標準数学ライブラリに入ったstd::log2()
関数を使えば、Nが2の何乗かを取得できます。
#include <iostream> #include <cmath> int main() { std::cout << std::log2(4.0) << std::endl; std::cout << std::log2(8.0) << std::endl; // 8は2の3乗 std::cout << std::log2(16.0) << std::endl; // 16は2の4乗 std::cout << std::log2(32.0) << std::endl; std::cout << std::log2(64.0) << std::endl; std::cout << std::log2(128.0) << std::endl; std::cout << std::log2(256.0) << std::endl; std::cout << std::log2(24.0) << std::endl; int x = 24; if (x <= 0 || (x & (x - 1)) != 0) { // xが2の乗数か判定 std::cout << "24 is not power of 2" << std::endl; } }
出力:
2 3 4 5 6 7 8 4.58496 24 is not power of 2
std::modf()
を使用していましたが、double integral_part; if (std::modf(std::log2(24.0), &integral_part) != 0) { std::cout << "24 is a not power of 2" << std::endl; }
@haxeさんに教えていただいて、以下のように修正しました。
int x = 24; if ((x & (x - 1)) != 0) { std::cout << "24 is not power of 2" << std::endl; }
x
がstd::numeric_limits::min()
の場合に2の乗数と判定されてしまうため、暫定対処として正の値かどうかのチェックを追加しました。本記事の本題とする、「一回の書き込みにすれば排他制御は必要ない」という部分が誤りであったため、本記事を取り下げます。
C++11から標準ライブラリに、並行プログラミングのための各種機能が入った影響は、入出力のライブラリにもあります。スレッドの存在を前提とした規定が追加されています。
[iostream.objects.overview] 27.4.1 p4には、以下のようにあります:
Concurrent access to a synchronized (27.5.3.4) standard iostream object’s formatted and unformatted input (27.7.2.1) and output (27.7.3.1) functions or a standard C stream by multiple threads shall not result in a data race (1.10). [ Note: Users must still synchronize concurrent use of these objects and streams by multiple threads if they wish to avoid interleaved characters. —end note ]
同期された標準iostreamオブジェクトの書式化された・されていない入力と出力の関数、および標準Cストリームに対する複数スレッドによる並行アクセスは、データ競合を引き起こさない。[注: 交互的な文字を避けるには、これらのオブジェクトとストリームの並行使用を複数のスレッドで同期させておく必要がある。]
最初の一文での「同期」とは、並行プログラミングでの同期ではなく、標準入力と標準出力の同期のことを指します。デフォルトで同期されます。パフォーマンスのために同期を意図的に外すこともあります。
注釈のところにある「交互的な文字」とは、スレッド1がos << 1 << 2;
、スレッド2がos << 3 << 4;
と書き込む場合に、出力順が1324
や1234
のように不定になり、スレッド間の出力が混在することを指します。複数スレッドからの並行アクセスによってiostreamのオブジェクトがおかしな状態になったりはしませんが、複数回の書き込みでの順序保証はありません。
結果が交互的になることを避けたい場合には、os << 1 << 2;
とos << 3 << 4;
をそれぞれ一度で書き込む必要があります。そのためには、文字列フォーマットの関数(Boost.Formatや、fmtlibなど)や、文字列ストリームなどを使用することで解決します。
void thread1() { // 解決策1 : 文字列フォーマット os << fmt::format("{0}{1}", 1, 2); } void thread2() { // 解決策2 : 文字列ストリーム std::stringstream ss; ss << 3 << 4; os << ss.str(); }
このようにすることで、出力は必ず1234
か3412
のどちらかになります。もちろん、ミューテックスを使用して一連の書き込みの順序を保証してもかまいません。
C++1zから、コンパイル時条件によって分岐するif constexpr
文が導入されます。これにより、再帰やヘルパ関数を書かなくて済むケースが多くなります。
D言語にあるstatic if
文のようなものです。
template <class T, class... Rest> void g(T&& p, Rest&&... rs) { if constexpr (sizeof...(rs) > 0) { g(rs...); // rs...が空のときのオーバーロードが不要 } }
else
の方にはconstexpr
は必要ありません。
テンプレート内で条件分岐した場合は、到達しなかったブロックはインスタンス化されません。
ただし、条件式内でのコンパイル時条件については全てインスタンス化され、短絡評価によって後ろの方の条件がインスタンス化されないことを期待するようなコードは書けないので注意してください。
if constexpr (has_value_type_v<T> && typename T::value_type())
このような条件式は、else節に進む場合にコンパイルエラーになります。このような条件を書きたい場合は、条件分岐を入れ子にする必要があります。
if constexpr (has_value_type_v<T>) { if constexpr (typename T::value_type()) { … } }
なお、constexpr if
でなくif constexpr
になっているのは、前者を採用すると構文として、else if
を書くときにconstexpr
が二重に必要になってしまうのを避けるためです。
// 採用された構文 if constexpr (cond) { } else if constexpr (cond) { } else { }
// 却下された構文 constexpr if (cond) { } constexpr else constexpr if (cond) { } constexpr else { }
最初に考えられていたstatic if
宣言と違って、if constexpr
文はスコープを導入します。また、型や関数を定義する条件分岐には使用できません。
// こういうことはできない。 // 条件によって関数や型の宣言・定義を変えるようなことはできないし、 // クラススコープでif constexpr文は使用できない struct X { if constexpr (cond) { void f(); using int32 = int; } else { void g(); } };
static if
declarationconstexpr if
constexpr if
: A slightly different syntaxこの記事の内容は、C++1zが正式リリースされる際には変更される可能性があります。正式リリース後には、C++日本語リファレンスサイトcpprefjpの以下の階層の下に解説ページを用意する予定です。
C++11でnoexcept
が導入されたことによって非推奨になっていたthrow
キーワードによる関数の例外仕様が、C++1zで削除されます。
throw
キーワードによる例外仕様とは、void f() throw(std::runtime_error)
のように、その関数がどの種類の例外を送出する可能性があるかを列挙する機能です。Javaの検査例外に近いものです。これはジェネリックプログラミングと相性が悪いことで問題視されていました。
C++1z以降、throw
キーワードを使用した例外仕様のコードはコンパイルが通らなくなりますのでご注意ください。throw()
はnoexcept
と等価な効果を持つ機能として、非推奨のまま残ります。例外送出のthrow
は変わりません。
// C++11から非推奨だがC++14まで合法。C++1zからコンパイルエラー //void f() throw(std::runtime_error); // C++11からは、noexceptを付けないことにより // 「なんらかの例外が送出される可能性がある」ことを表現するか、 void f(); // コンパイル時の条件式によって、なんらかの例外が送出される可能性の有無を指定する void f() noexcept(cond);
throw
キーワードによる例外仕様の削除にともない、関連して非推奨だった以下の機能が削除されます:
これらの機能は、例外仕様で指定されていない例外が送出された場合に発生するエラーを検知するためのものですが、noexcept
キーワードによる例外仕様では、例外は送出されるかされないかのみで、なんの例外が送出される可能性があるかは扱わないため、この種のエラーは発生しません。
この記事の内容は、C++1zが正式リリースされる際には変更される可能性があります。正式リリース後には、C++日本語リファレンスサイトcpprefjpの以下の階層の下に解説ページを用意する予定です。
C++1zからは、noexcept
による関数の例外仕様が、型の一部として扱われるようになります。
互換性のためにnoexcept
な関数ポインタから非noexcept
な関数ポインタには変換できます。しかし、非noexcept
な関数ポインタからnoexcept
な関数ポインタには、変換できません。
void f() noexcept; void g(); void(*p1)() noexcept = f; // OK void(*p2)() = f; // OK : noexceptから非noexceptへの変換 //void(*p3)() noexcept = g; // コンパイルエラー : 非noexceptからnoexceptに変換できない
これは、ラムダ式も同様になります。
void(*p1)() noexcept = []() noexcept {}; // OK void(*p2)() = []() noexcept {}; // OK //void(*p3)() noexcept = [](){}; // コンパイルエラー
noexcept
の違いによって関数をオーバーロードすることはできません。(戻り値の型と同様)
この仕様による破壊的な影響として、C++14まで有効だった以下のコードは、C++1zではコンパイルエラーになります:
void g1() noexcept; void g2(); template<class T> int f(T*, T*); int x = f(g1, g2); // コンパイルエラー : g1とg2の型が一致しない (関数テンプレートの推論失敗)
この記事の内容は、C++1zが正式リリースされる際には変更される可能性があります。正式リリース後には、C++日本語リファレンスサイトcpprefjpの以下の階層の下に解説ページを用意する予定です。
C++11では<cmath>
にいろいろ関数が追加されていますが、丸め演算も増えています。
C++11で最近接偶数への丸めを使いたい場合は、std::nearbyint()
(near by int)という関数がありますので、それを使いましょう。
#include <iostream> #include <cmath> int main() { std::cout << std::nearbyint(2.5) << std::endl; // 2が出力される }
四捨五入も最近接偶数への丸めも、どちらも大きなくくりでは「round to nearest integer (最も近い整数への丸め)」に該当します。この2つの丸め方式は、中間値(0.5)の場合に動作が異なります:
最近接偶数への丸めの必要性は、「「銀行家の丸め」とは何か - 会計SEのメモ」このあたりの記事がわかりやすいでしょう。
std::nearbyint()
関数の実際の動作として、四捨五入になるか最近接偶数への丸めになるかは、ISO/IEC 60559 (IEEE 754)規格の推奨によって決まります。この規格ではround to nearestの丸め方式として最近接偶数への丸めを推奨しているため、この規格に準拠した環境では、std::nearbyint()
関数は最近接偶数への丸めとして動作します。
補足として、
std::nearbyint()
関数は、スレッドローカルな丸めモード状態に依存して丸め方式が変わるので注意std::rint()
というものもあるが、丸めが発生したときに浮動小数点例外が発生するので使いにくそう。ISO/IEC 60559 (IEEE 754)規格で、std::nearbyint()
関数とstd::rint()
関数両方の動作が定義されているらしいC++1zでは、「畳み込み式 (Fold expressions)」という機能が入ります。これは可変引数テンプレートのパラメータパックに対して集計操作を行うためのものです。
たとえば、整数のシーケンスを与えてその合計値を求める計算は、以下のように書けます:
#include <iostream> template <class... Args> int sum(Args... args) { // 「... @ args」が畳み込み式。@の部分は任意の二項演算子 return (... + args); } int main() { int x = sum(1, 2, 3, 4, 5); std::cout << x << std::endl; // 15 }
このような計算はこれまで再帰を使用して実装する必要がありましたが、より簡潔に書けるようになりました。
ここでは、args
パラメータパックに含まれる全要素を、+
二項演算子を使って集計しています。...
を演算子の左辺に置いているので、左から畳み込みが行われます。計算過程としては、以下のようになります:
1 + 2 3 + 3 6 + 4 10 + 5
...
を演算子の右辺に置くと、右から畳み込まれます:
template <class... Args> int sum(Args... args) { return (args + ...); }
計算過程:
4 + 5 3 + 9 2 + 12 1 + 14
accumulate()
アルゴリズムと同様に、初期値をくっつけることもできます:
template <class... Args> int sum(Args... args) { return (0 + ... + args); }
畳み込み式で使用できるのは、以下の二項演算子です:
+ - * / % ^ & | ~ = < > << >> += -= *= /= %= ^= &= |= <<= >>= == != <= >= && || , .* ->*
前述した例の+
演算子以外ですと、たとえば条件式のシーケンスに対して「全てtrue
か」「いずれかがtrue
か」「全てfalse
か」を調べる関数は、以下のように書けます:
#include <cassert> // 全てのパラメータがtrueならtrueを返す template <class... Args> bool all(Args... args) { return (true && ... && args); } // いずれかのパラメータがtrueならtrueを返す template <class... Args> bool any(Args... args) { return (... || args); } // 全てのパラメータがfalseならtrueを返す template <class... Args> bool none(Args... args) { return !any(args...); } int main() { bool a = all(true, true, true); bool b = any(false, true, false); bool c = none(false, false, false); assert(a == true); assert(b == true); assert(c == true); }
パラメータパックがからの場合のデフォルト値は、演算子によって変わります:
演算子 | 値 |
---|---|
* |
1 |
+ |
int() |
& |
-1 |
| |
int() |
&& |
true |
|| |
false |
, |
void() |
この表に記載されていない演算子を空のパラメータパックに対して適用すると、コンパイルエラーになります。
この記事の内容は、C++1zが正式リリースされる際には変更される可能性があります。正式リリース後には、C++日本語リファレンスサイトcpprefjpの以下の階層の下に解説ページを用意する予定です。
C言語から引き継がれた「トライグラフ (trigraph)」という機能は、ASCIIより小さなISO/IEC 646という文字コードでもプログラムが書けるように用意された機能で、いくつかの文字の代替表現を使用できるようにするものです。
トライグラフ表現 | 置き換え後の文字 |
---|---|
??= |
# |
??/ |
\ |
??' |
^ |
??( |
[ |
??) |
] |
??! |
| |
??< |
{ |
??> |
} |
??- |
~ |
以下のトライグラフを使用したコードは、
??=define arraycheck(a,b) a??(b??) ??!??! b??(a??)
以下のように置き換えられます:
#define arraycheck(a,b) a[b] || b[a]
文字コードの問題がなくなった現代では、トライグラフは不要になりました。ユーザーにとってわかりにくいこともあり、多くのコンパイラがトライグラフをデフォルトで無効にしたり警告を出力したりといった対応をしていました。
C++1zの策定作業にともない、トライグラフの利用状況が大規模に調査されました。この機能が使用されていることはほとんどないということがわかり、C++1zからはトライグラフが削除されることが決定しました。
今後、トライグラフを使用したC++コードはコンパイルが通らなくなりますので、注意してください。C言語の方はC11時点でまだ残っています。
この記事の内容は、C++1zが正式リリースされる際には変更される可能性があります。正式リリース後には、C++日本語リファレンスサイトcpprefjpの以下の階層の下に解説ページを用意する予定です。
C++1zから、タプルやクラスを分解する「構造化束縛 (Structured Binding)」という機能が入ります。他の言語では多重代入のように呼ばれているもので、C++11標準ライブラリのstd::tie()
関数の代わりとして使用できます。
以下は、タプルを返す関数の戻り値を、分解して受け取るコードです:
tuple<int, string> f(); // aにはintの値、bにはstringの値が代入される auto [a, b] = f();
構造化束縛には、auto
プレースホルダーを型として指定したあと、角カッコ [ ]
内に、分解した値を受け取る変数名のリストを記述します。各変数に対して個別に具体的な型を指定することはできません。
タプルの場合は、std::tuple_size<T>::value
という式が妥当な型が対象となり、タプルのインタフェースget<I>(t)
とstd::tuple_element<I,T>::type
が使用できる型であれば、std::tuple
と同様に構造化束縛で分解できます(ADLで呼び出すため、get
にはstd::
が付いていない)。標準ライブラリでは、std::pair
とstd::array
もタプルとして分解できます。
これを使用して、map::insert()
の戻り値を簡単に受け取れたりしますね。
auto [iter, success] = m.insert(x); // iterは、挿入された要素を指すイテレータ // successは、挿入に成功したかを表すbool値 (重複時に失敗する)
構造化束縛はタプルだけでなく、クラスも分解できます。クラスは、非静的publicメンバ変数が列挙されます(public基本クラスのものを含む)。そのクラスは、無名共用体メンバを持ってはいけません。
struct Point { int x, y; }; Point f(); // xにはPoint::xの値、yにはPoint::yの値が代入される const auto [x, y] = f();
メンバ変数は、ビットフィールドになっていても分解できます。
struct X { int flag : 1; int counter : 15; }; X f(); const auto [flag, counter] = f();
組み込み配列を分解することもできます。その際、受け取る変数名のリストは、配列の要素数と同じ個数だけ指定する必要があります。要素数が一致しない場合はコンパイルエラーになります:
// 配列の各要素を取得。 // 受け取る個数が配列の要素数と一致していなければならない int ar[] = {3, 1, 4}; auto [a, b, c] = ar;
変数を参照として受け取りたい場合は、auto
のあとに参照の修飾をします。以下は、範囲for文でstd::map
オブジェクトの要素をキーと値に分解するコードです:
std::map<int, string> m; for (const auto& [key, value] : m) {}
属性は、分解した個別の変数に対しては指定できず、全体に対して指定することになります:
[[maybe_unused]] auto [a, b] = f();
この記事の内容は、C++1zが正式リリースされる際には変更される可能性があります。正式リリース後には、C++日本語リファレンスサイトcpprefjpの以下の階層の下に解説ページを用意する予定です。