2013-01-24

C++ 2013-01 mailingの簡易レビュー

ISO/IEC JTC1/SC22/WG21 - Papers 2013が公開された。今回はそれほど多くはない。

C++11の参考書は、今年中には出せるようにしたい。この際、未完成でも公開してしまうべきだと思う。

N3493: Compile-time integer sequences

Variadic Templateのとあるイディオムをサポートするための標準のクラステンプレートを追加する。

以下のようなコードを考える。

template<class... T>
  void f(T... t);

このパラメーターパックtのすべての要素に対して、expr( コンマで区切られた各要素 )という式を適用したい場合は、単にexpr( t ... )と書けばよい。

tupleの場合はどうだろうか

template<class... T>
  void foo(std::tuple<T...> t);

このtupleのすべての要素を、コンマ区切りにしてexprに渡したい場合どうすればいいのだろうか。expr( t... )と書くことは出来ない。では、どうすればいいのか。再帰的なメタプログラミングを書くしかない。論文のクラステンプレートを使う方法が気にいらなかったので、自分なりに書くと以下のようになった。


struct unpack_element
{

struct call_expr
{
    template < std::size_t, typename Tuple, typename ... Types > 
    static void invoke( Tuple &&, Types && ... pack )
    {
        expr( std::forward< Types >( pack ) ... ) ;
    }
} ;


struct call_apply
{
    template < std::size_t I, typename Tuple, typename ... Types >
    static void invoke( Tuple && t, Types && ...  pack )
    {
        apply< I + 1 >( std::forward<Tuple>(t), std::forward<Types>(pack)..., std::get< I >( t ) ) ;
    }
} ;

template < std::size_t I, typename Tuple, typename ... Types >
static void apply( Tuple && t, Types && ... pack )
{

    std::cout << Demangle( typeid( std::tuple<Types...> ) ) << std::endl ;
    constexpr std::size_t i = std::tuple_size< Tuple >::value ;
    std::conditional< I == i, call_expr, call_apply>::type::template invoke<I>( std::forward< Tuple >(t), std::forward<Types>(pack)... )  ;
}

} ;

template < typename Tuple >
void foo( Tuple && t )
{
    unpack_element::apply<0>( std::forward< Tuple  >( t ) ) ;
}

クラステンプレートは使わなかったが、結局クラスは使うことになってしまった。クラススコープに入れないとcall_applyがapplyを呼べないのだ。テンプレートの相互再帰はどうかと思うが、個人的にはこちらのほうが読みやすいのではないかと思う。

実は、この問題は、眼からウロコの解決方法があるのだ。まず以下のようなクラステンプレートを定義する。

template< std::size_t ... >
struct index_seq { } ;

このindex_seqのテンプレートパラメーターをパラメーターパックにして受け取れば、N個の要素を持つstd::tuple用の、[0, 1, 2, ... , N]までのパラメーターパックが受け取れる。あとは、std::getで普通に使うだけだ。

簡単に使うために、0, 1, 2, ..., Nまでのパラメーターパックを生成するmake_index_seqを定義する。


template < std::size_t N, typename T >
struct make_index_seq_impl ;


template < std::size_t N, std::size_t ... I  >
struct make_index_seq_impl< N, index_seq< I ... > >
{
    using type = typename make_index_seq_impl< N-1, index_seq< N-1, I... > >::type ;
} ;


template < std::size_t ... I  > 
struct make_index_seq_impl< 0, index_seq< I ... > >
{
    using type = index_seq< I... > ;
} ;

template< std::size_t N >
using make_index_seq = typename make_index_seq_impl< N , index_seq< > >::type ;

これで準備は完了だ。あとは使うだけ。

template < typename Tuple, std::size_t ... I >
void foo_impl( Tuple && t, index_seq< I ... > )
{
    expr( std::get<I>(t)... ) ;
}

template < typename Tuple,
    typename Indices = make_index_seq< std::tuple_size<Tuple>::value >
>
void foo( Tuple && t )
{
    foo_impl( std::forward<Tuple>(t), Indices() ) ;
}

なんと、このイディオムにより、再帰的なメタプログラムを書かなくても、簡単にTupleのすべての要素をコンマ区切りにできる。

ここでは簡易的にindex_seqのみを実装したが、規格では、より汎用的なinteger_seqテンプレートを定義し、そこからint_seq, uint_seq, index_seq(std::size_t)を作っている。

この機能はPythonにヒントを得ているのだそうだ。

PDF注意:N3494: A proposal to add special mathematical functions according to the ISO/IEC 80000-2:2009 standard

標準ライブラリに大量の数学的関数を追加する提案。数学的関数を追加することで、科学者にもC++を訴求する狙いがあるのだとか。

N3495: inplace realloc

アロケータ―にrealloc機能を導入する提案。

古き良きreallocは、確保したメモリ領域を、そのまま縮めたり伸ばしたりできる機能である。もちろん、不可能な場合は、単なる新たな領域確保とコピーになるが、実装次第では可能であり、無駄の削減にもなりうる。

残念ながら、C++のアロケーターには、realloc機能を提供するインターフェースがない。そこで、そのようなインターフェースを追加する。

N3497: Runtime-sized arrays with automatic storage duration (revision 4)

実行時にサイズを指定できる自動ストレージ上の配列を許す提案。つまり、配列の定義で、コンパイル時定数を使わなくても良くなる。すでに、C99には同等の機能がある。

void f( std::size_t n )
{
    int a[n] ; // nは実行時に決定される。
} 

単に自動ストレージ上に配置されるだけならば、allocaやstd::arrayを使えばいい。しかし、allocaはタイプセーフではなく、コンストラクターやデストラクタ―も動かない。やはり、生の配列を使いたい需要が多い。

また、実行時に長さが与えられる配列の確保に失敗した場合は、bad_array_length型が例外としてthrowされる。

N3498: Core Issue 1512: Pointer comparison vs qualification conversions (revision 2)

C++におけるポインターの比較に対する変更。歴史的経緯から、C++のポインターの比較は、非常に分かりにくい。

この提案は、nullptrとポインター型や0との大小比較をill-formedにする。

void f(char * p)
{
  if (p > 0) { ... }
  if (p > nullptr) { ... }
}

また、T **とconst T **の比較をwell-formedにする。

void g(int **p1, const int**p2)
{
   if (p1 == p2) { ... }
}

N3499: Digit Separators

長い数値リテラルは読みづらいので、セパレーターを挟めるようにしようという提案。まだまだ議論は続く。問題は文法だ。数値リテラルのような基本的な文法を変更するのは難しい。本物のC++コンパイラーなら、いまさら文法はどうにでもなるが、世の中には簡易的なパーサーを使っているツールが山ほどある。そのようなツールを壊し、さらに修正に多大な労力を必要とするのは困る。

また、C++で数値リテラルというと、プリプロセッサーの定義も別にある。

英語で伝統的に用いられてきたのは、コンマだが、残念ながら、C++では既存の文法との兼ね合いからコンマは使えない。では、どの文字を区切り文字として使うのか。

Bjarne Stroustrupは単に空白文字を提案した。

int x = 123 456 ; // 123456

問題は、プリプロセッサーの定義も修正が必要になることだ。また、十六進数リテラルで[a-f]の範囲の文字を使うリテラルは、文法上の曖昧性の問題がある。また、単に空白文字で区切られた文字列を単語と認識して抽出するようなツールの存在もある。

Ville Voutilainenはグレイブアクセントを提案した。

int x = 123`456 ; // 123456

グレイブアクセントは、C++のBasic Character Setにはない文字である。したがって、新たに追加することになる。グレイブアクセントの利点としては、従来C++には存在しなかった文字なのだから、既存のC++コードとの互換性の問題がないということだ。

ただし、あくまでC++コードとの互換性だけだ。たとえば、C++コードにメタデータを埋め込み、別のツールで処理させるような場合、C++ではたまたま使われていないグレイブアクセントは好都合だ。グレイブアクセントをC++に追加すると、そのような既存のツールを壊してしまう。また、既存のプリプロセッサーもグレイブアクセントを認識しないので修正が必要になる。

シングルクオート

Daveed Vandevoordeはシングルクオートを提案した。

int x = 123'456 ; // 123456

しかし、プリプロセッサーの問題は残る。Vandevoordeの専門分野なので自ら解説している。

そういえば、VandevoordeのファーストネームのスペリングはDaveedの方が正しいのだろうか。C++ TemplatesではDavidと表記している。それはさておき。

アンダースコア

既存のいくつかの言語は、アンダースコアを数値リテラルの区切り文字に用いている。例えば、Ada、VHDL 、Verilogなどだ。

int x = 123_456 ; // 123456

私は個人的にアンダースコアが最も気に入っているのだが、残念ながらアンダースコアには問題が多い。

ユーザー定義リテラルと文法が曖昧になるのだ。

int x = 0xabc_def ;

これは、0xabcefなのか、あるいは0xabcと_defというユーザー定義リテラルを記述したものなのか。

この曖昧性を避けるために、ダブルアンダースコアを使ってはどうかという珍妙な意見まで出された。

また、John Spicerは、名前解決のルールに手を加えて、0xabc_defに対しては、まず_defという名前のユーザー定義リテラルを探し、見つからない場合は、数値リテラルとして解釈するなどという これまた珍妙な意見を出した。

また、スコープ演算子(::)やらダブル小数点(..)やらバックスラッシュ(\)やらと、他の区切り文字の意見も出された。

果たして、どこに落ち着くことになるのやら。

N3500: New assert variants

既存のassertは、メッセージを付加できない。そのため、メッセージを付加できるassertを追加する。

void f( int x )
{
    assert_msg( x, u8"0はだめよん" ) ;
}

N3505: Filesystem Library Proposal (Revision 4)

Filesystemライブラリの提案の改訂版。Boostを元にしている。変更点としては、UTF-8をデフォルトで受け付けるようにした。

N3506: A printf-like Interface for the Streams Library

ストリームで使えるprintfフォーマット。以下のように使う。

std::cout << putf("Smoke me a %s.", "Kipper") << std::endl ;

なんのひねりもなく、従来のprintfのフォーマットをそのまま使えるようにしただけだ。printfのフォーマットは非常に有名なので、なかなか変えづらい。

個人的には、Boost::formatあたりを標準化して欲しいのだが。

N3507: A URI Library for C++

C++でURIを処理するためのライブラリ。名前通り。あくまで、単なる文字列としてのURIの処理だ。wgetライブラリではない。

N3508: Any Library Proposal

BoostのAnyを標準ライブラリにいれる提案。

N3509: Operator Bool for Ranges

STLのbasic_string、コンテナー、rangeに、explicit operator boolのオーバーロードを付け加える提案。

空のときはfalse、そうでないときはtrueを返す。

boolはtrueかfalseを表し、数値はゼロか非ゼロを表し、ポインターはnullか非nullを表す。とすれば、文字列やコンテナーやRangeは空か非空を表すことができる。

N3510: Proposing std::split()

文字列分割のライブラリであるstd::splitの提案。

言うまでもなく、文字列分割はプログラミングの基本的な処理である。分割した文字列はイテレーターで回したい。とすると、たとえば以下のような関数を書くことになる。

std::vector<std::string> my_split(const std::string& text, const std::string& delimiter);

標準ライブラリにはこのような関数がないため、企業、団体、プログラマーごとに独自実装が生まれる。お互いに互換性はなく、機能的にもまちまちである。

そのため、汎用的に使えるsplitを標準ライブラリに導入する。このstd::splitは、std::string_refをうけて、分割した結果をRangeで返す。また、デリミタ―は抽象的なコンセプトであり、単なる文字指定や、正規表現や、あるいは自前で実装したデリミタ―要求を満たすカスタムデリミタ―も使える。

N3511: exchange() utility function

atomicライブラリには、atomic_exchangeという関数がある。これは、オブジェクトに新しい値を代入し、古い値を返す関数である。この操作は、非アトミックなコードでも有益である。そのため、非アトミック版の汎用的なexchange関数を標準ライブラリにいれる提案。

N3512: string_ref: a non-owning reference to a string, revision 2

文字列を所有するのではなく、その参照を保持する、std::string_refライブラリの提案の改訂版。

N3513: Range arguments for container constructors and methods, wording revision 2

C++11には惜しくも入らなかったRangeライブラリ。基本的には、イテレーターのペアを返すコンセプトである。

PDF注意:N3514: A Proposal for the World's Dumbest Smart Pointer

タイトル:世界一おバカなスマートポインターの提案

スマートポインターというイディオムは、ポインターという低級な機能を、高級な機能に押し上げてくれる。スマートポインターの機能はいろいろある。unique_ptrや時代遅れのC++03のauto_ptrのような、デストラクタ―が走るタイミングで自動的にdeleteしてくれるスマートポインターもあるし、shared_ptrのような、参照カウントが0になった段階でdeleteしてくれるスマートポインターもある。また、optionalのような、nullになりえるポインターを抽象的に表現しているスマートポインターもある。

スマートポインターの機能は様々だが、いずれも、ポインターのように振る舞うが、ポインターのある機能を抽象化しているということには変わりない。

ところで、生のポインターはどうだろうか。ポインターの参照先のオブジェクトの寿命管理は他所で行われている場合、観測の目的で、生のポインターを使うということは多い。

しかし、そのような場合でも、生のポインターを使わずに、所有しないスマートポインター経由で使ったほうが、コードが読みやすくならないだろうか。機能的には何も変わらない。単に可読性の問題である。

しかし、そのようなスマートポインターは、既存の標準ライブラリの枠組みでは作れない。unique_ptrに何もしないデリーターを組み合わせるのは、うまくいかない。何故ならば、unique_ptrはもともと所有する前提で設計されており、コピーができずムーブのみ可能だからだ。一方、観測のための所有しないポインターは、自由にコピーできてしかるべきだ。

そこで、所有しないポインターというイディオムを表現する、あまり賢くない、それ自体は何もしない、つまりはおバカなスマートポインターを標準ライブラリにいれてはどうか。

この所有しないポインターのイディオムは正しいのかどうか、色々議論がある。C++標準委員会の重鎮でも、所有しない観測目的のポインターは生ポインターで苦労した覚えはないという意見を出すメンバーがいた。とはいえ、まだC++の標準ライブラリは、すべての種類のスマートポインターの含めえたわけではないので、所有しないスマートポインターをいれてもいいのではないか。

そういうわけで、世界一おバカなスマートポインターというタイトルになっている。

ちなみに、PDFは世界で二番目におバカなフォーマットである。一番目はMS Officeのような邪悪なプロプライエタリのフォーマットである。

PDF注意:N3515: Toward Opaque Typedefs for C++1Y

Opaque Typedef、あるいはStrong Typedefとも呼ばれている提案。typedef名が別の型として認識される不透明で強いtypedefを作ろうという提案である。

時として、機能的には同じだが、型としては別物だと認識してほしい場合があるものだ。これは、型が機能ではなく、意味を表現する場合だ。

N3520: Critical sections in vector loops

以前、ベクトルループの中ではクリティカルセクションは禁止されていたが、使いたいという要望に答えて、ベクトルループの中でクリティカルセクションを使う部分をマークすることで、使えるようにする提案。

今回は並列化に関する論文は少ないが、やはり並列化の言語でのサポートは次のC++ではだいぶ現実的になっている。

N3521: convert() utility function

明示的な型変換が必要な場面を暗黙的に型変換するための関数テンプレートconvertの追加。

以下のコードは動かない。

std::unique_ptr<int> f()
{
    return new int ;
}

なぜならば、unique ptrの変換関数はexplicitだからだ。以下のコードも動かない。

std::unique_ptr<int> f()
{
    return {new int} ;
}

しかし、{}で囲っているのだから、もう十分明示的と言えるのではないか。なぜ以下のように書かなければならないのか。

return std::unique_ptr<int>( new int ) ;

このために、一度暗黙な型変換を行うクラスを経由して、明示的な型変換を暗黙の型変換にできるテンプレート関数を追加する提案。これを使えば、以下のように書ける。

return convert( new int ) ;

トリックは、すでに述べたように、一度別のクラスを経由することにある。

template<typename T>
struct Converter {
  T source_;
  Converter(T source) : source_(std::forward<T>(source)) {}
  template<typename U, decltype(static_cast<U>(std::forward<T>(source_)), 1)=0>
  /*implicit*/ operator U() {
    return static_cast<U>(std::forward<T>(source_));
  }
};
template<typename T>
Converter<T> convert(T&& source) {
  return Converter<T>(std::forward<T>(source));
}

これにより、explicitな変換関数を持つクラスでも、暗黙的に型変換できる。

まあ、トリックとしては昔から知られていたが、悪用が怖い。

1 comment:

Anonymous said...

> Daveedの方が正しいのだろうか
オランダ語の David なので英語話者は Daveed で調音してねということ。