2014-10-pre-Urbanaのレビュー: N4200-N4209
N4200: Feature-testing recommendations for C++
C++の機能が実装されているかどうかを確かめるプリプロセッサーマクロの提案。
現実のコンパイラーは、言語の新機能をすこしづつ実装していく。そこで、現実のコードでは、以下のようなコードが存在する。
struct X
{
X() { ... }
X( X const & rhs ) { ... }
#ifdef __cpp_rvalue_references
X( C && rhs ) { ... }
#endif
} ;
このようなマクロは、現実のコードで頻出するので、規格で定義したほうがよい。そこで、規格にこのようなマクロがあらかじめ定義されているようにする提案。たとえば、この__cpp_rvalue_referencesというマクロは、実装がrvalueリファレンスに対応している場合のみdefineされる。
詳しくは論文を読んでもらうとして、各機能に対応するマクロ名がある。
また、特定のincludeファイルが存在するかどうかを調べる__has_include()や、特定のattributeがあるかどうかを調べる__has_cpp_attributeがある。
#if __has_cpp_attribute(deprecated)
[[ deprecated ]]
#endif
void f() ;
#if __has_include(<unordered_map>)
#include <unordered_map>
#else
#include "hash_map"
#endif
必要悪とはいえ・・・プリプロセッサーマクロに頼る機能には複雑な思いがある。
N4201: Alignment helpers for C++
アライメント調整のための関数を追加する提案。
is_align( x, a )は、xが0か、xがaの倍数の場合にtrueを返す。
align_up( x, a )は、x以上の最小の数nで、is_align(n, a)を満たすnを返す。
align_down( x, a )は、x以下の最大の数nで、is_align(n, a)を満たすnを返す。
int main()
{
std::is_aligned( 128, 64 ) ; // true
std::is_aligned( 32, 64 ) ; // false
std::align_up( 128, 64 ) ; // 128
std::align_up( 128, 256 ) ; // 256
std::align_down( 128, 64 ) ; // 64
std::align_down( 128, 256 ) ; // 128
}
xは、整数型とポインター型でオーバーロードされているので、アドレスのアラインにも使える。
int main()
{
void * ptr = std::malloc( 1000 ) ;
// 16バイトにアライメントされているか?
bool b = std::is_aligned( ptr, 16 ) ;
// 256バイトにアライメント調整されたポインターを得る
void * aligned_256 = std::align_up( ptr, 256 ) ;
// 128バイトにアライメント調整しなおしたポインターを得る
void * aligned_128 = std::align_down( aligned_256, 128 ) ;
またN4201はアライメントキャストを提案している。これは、reinterpret_castとアライメント調整を同時に行うキャストである。
void f( void * ptr )
{
// alignof(int)にalign_upしてint *型にreinterpret_castしたポインターを得る
int * int_ptr = std::align_up_cast< int * >( ptr ) ;
// alignof(short)にalign_downしてshort *型にreinterpret_castしたポインターを得る
short * short_ptr = std:align_down_cast< short * >( ptr ) ;
// 128にalign_up
int * int_ptr = std::align_up_cast< int * >( ptr, 128 ) ;
}
これらの関数は、SIMDや非同期IOやドライバーなど、様々な分野で使われる。いずれも一行で実装できるものであるが、標準ライブラリに存在することによって、必要になるたびにソフトウェアごとにプログラマーが独立してググり、書き、テストする手間を省くことができる。
N4202: Strongly Typed Bitset
C++11から追加されたbitset<N>は、ビット列を扱う効率的で抽象度の高いコンテナーとして設計された。しかし、現実のビット列を扱う分野では、アライメントを指定する必要があったり、サイズが重要であったりして、bitsetは事実上使えない状態になっている。
bitsetの内部的なストレージのアライメントやサイズを指定する方法はない。例えば、x86-64のGCCにおけるbitsetの実装は、最小でも8バイトのサイズがある。
bitsetに新たなテンプレートパラメーターを付け加えて、bitset<N,T>とする、Tはalignof(T)とsizeof(T)のための型だ。このbitset<N,T>は、以下のような特性を持つ。(小文字のnがよくわからないが)
static_assert(is_integral<T>::value);
static_assert(alignof(bitset<N,T>) == alignof(T[N / (sizeof(T) * CHAR_BIT) + (n % (sizeof(T) * CHAR_BIT) != 0)];
static_assert(sizeof(bitset<N,T>) == sizeof(T[N / (sizeof(T) * CHAR_BIT) + (n % (sizeof(T) * CHAR_BIT) != 0)];
ビット列を表現するための内部的なストレージの型として、underlying_typeというネストされた型名を追加する。
using type = std::bitset<64>::underlying_type
また、bitsetのエイリアステンプレートとして、速度を重視したfast_bitsetと、サイズを重視したsmall_bitsetを追加する。
int main()
{
// 速度を重視したアライメントやサイズになる
std::fast_bitset<20> fast_bits ;
// 速度よりも可能な限り最小サイズを重視
std::small_bitset<20> small_bits ;
}
N4203: Fast ASCII Character Manipulation
タイトルの通り、高速にASCII文字を処理するライブラリの提案。
isdigit, isxdigit, islower, isupper, isalpha, isalnumなどの各種の分類のための関数、todigit, tolower, toupperなどの変換関数、int型を数字一文字に変換するfromdigitもある。
void f( char c )
{
std::ascii::isdigit(c) ;
std::ascii::islower(c) ;
char C = std::ascii::touppwer(c) ;
}
C言語から受け継いだ<cctype>にもほぼ同等の関数群があるが、この論文はcctypeが極めて遅いという設計上の問題を挙げている。
<cctype>は、実行時に設定されるロケールによって処理が異なる。たとえば、isspaceはロケールによって空白文字であると認識される文字が変わる。そもそもASCII互換ですらない文字コードすら扱えるようになっている。このため、cctypeの実装には動的な分岐が必要で、肥大化して遅く、インライン展開もできない。一方、この提案は、ASCII文字を扱うことだけを前提に設計されている。実装はconstexpr関数であり、インライン展開でき、コンパイラーの最適化次第でベクトル化も可能となる。
その他、isXXX系の関数は<cctype>とは違い、int型ではなくbool型を返すことや、char, wchar_t, char16_t, char32_tに対するオーバーロードがあることなど、より洗練された設計になっている。
N4204: C++ Latches and Barriers (post Rapperswil)
スレッドの動機に使うラッチとバリアーライブラリの提案。
ラッチは、カウンターを持つ、arrive()かcount_down(N)で、カウンターを減少させる。カウンターが0になると同期条件が満たされる。同期条件が満たされると、wait()で待っているスレッドのブロックが解かれる。
int main()
{
// カウンターは10
std::latch c(10) ;
for ( int i = 0 ; i != 10 ; ++i )
{
std::thread t( [&](){
// 処理
c.arrive() ;
} ) ;
t.detach() ;
}
// カウンターが0になるまでブロック
c.wait() ;
}
arriveは、ひとつのスレッドは一度しか呼び出すことができない。count_downにはそのような制限がなく、同一スレッドから何度でも呼び出せる。count_downは、スレッドプールなどの同一スレッドで複数の単位の処理をしている場合に使える。
ラッチは使い捨てで、再利用できない。バリアーは、同期条件を満たした後にリセットされ、再利用できる。
flex_barrierはbarrierに似ているが、コンストラクターで同期条件を満たした場合に呼ばれる関数オブジェクトを指定できる。
N4205: Working Draft, C++ Extensions for Concepts
コンセプトのドラフト。
N4206: C++ Standard Evolution Active Issues List
N4207: C++ Standard Evolution Completed Issues List
N4208: C++ Standard Evolution Closed Issues List
C++の新機能を扱うEWGで現在認識している問題、解決済みの問題、却下された問題の一覧。
N4209: A Proposal to Add a Const-Propagating Wrapper to the Standard Library
const性を伝播させるためのラッパー、propagate_constライブラリの提案。
constなメンバー関数からポインターを経由して別のメンバー関数を呼び出す場合、const性が伝播しない。
struct A
{
void f() {}
void f() const {}
} ;
struct B
{
std::unique_ptr<A> ptr ;
B() : ptr( std::make_unique<A>() { }
void f() { ptr->f() ; }
void f() const { ptr->f() ; } ;
}
int main()
{
B b ;
b.f() ; // call B::f(), A::f()
B cb ;
cb.f() ; // call B::f() const, A::f()
}
このような例でconst性をメンバーに伝播させたい場合に使えるラッパーライブラリを提案している。
struct A
{
void f() {}
void f() const {}
} ;
struct B
{
std::propagate_const<std::unique_ptr<A>> ptr ;
B() : ptr( std::make_unique<A>() { }
void f() { ptr->f() ; }
void f() const { ptr->f() ; } ;
}
int main()
{
B b ;
b.f() ; // call B::f(), A::f()
B cb ;
cb.f() ; // call B::f() const, A::f() const
}
ドワンゴ広告
この記事はドワンゴ勤務中に書かれた。
ところで、歌舞伎座タワーのB2Fのエレベーターの前にいる案内員は、一般の観光客とドワンゴ社員を人目で見分ける能力を有しているらしく、観光客がやってくると観光客向け5F行きエレベーターのボタンを、ドワンゴ社員がやってくると7F直通エレベーターのボタンを押す。いったい彼らはどうやってドワンゴ社員かどうかを見分けているのだろうか。
ドワンゴは本物のC++プログラマーを募集しています。
CC BY-ND 4.0: Creative Commons — Attribution-NoDerivatives 4.0 International — CC BY-ND 4.0