C++標準化委員会の文書集、2015-04 pre-Lenexa mailingsのレビュー: N4381-N4389
ISO/IEC JTC1/SC22/WG21 - Papers 2015
N4381: Suggested Design for Customization Points
現在、C++の標準ライブラリにはいくつかのユーザー側で挙動を変更できる箇所が存在する。
- swap
- begin
- end
だ。文面の解釈次第では、iter_swapも該当するかもしれないとのことだ。
論文は、標準ライブラリのうち、これらの挙動を差し替え可能な部分を、カスタマイゼーションポイント(Customization point)と名づけている。
この関数テンプレートは、std名前空間で定義されていて、汎用的な実装になっている。もし、独自のユーザー定義型に独自のswap/begin/end実装を書きたい場合
struct X
{
int data[100] ;
} ;
その型の名前空間スコープに、同名の関数/関数テンプレートを書けばよい。
int * begin( X & x ) { return data ; }
int * end( X & x ) { return data + 100 ; }
こうすることによって、ADLで適切な関数が選ばれる。
問題は、標準のカスタマイゼーションポイントと、ユーザーの提供するカスタマイゼーションポイントを合わせて呼び出すには、std::beginのようにqualified nameで呼び出すことはできない。現行のカスタマイゼーションポイントは、ADLを使っているので、以下のように呼ばなければならない。
template < typename Container >
void f( Container && c )
{
using std::begin ;
using std::end ;
auto iter = begin( c ) ;
auto end = end( c )
}
こうすることによって、std::begin, std::endという名前をname lookupで発見できるようにしたうえで、unqualified name lookupを行うと、ADLも働き、見つかった名前の中でオーバーロード解決が行われる。
しかし、このようにusing宣言を書いてからunqualified nameを使うやり方は、その原理の説明まで含めると、規格の詳細まで踏み込まねばならず、面倒だ。普通にstd::funcのようにqualified nameで呼び出してもこのような挙動になってほしい。
このようなcustomization pointは、今後も追加される見込みであるから、将来追加されるcustomization pointは、関数オブジェクトにして、その中でADLを用いたディスパッチにしようというという提案。
namespace std {
namespace __detail {
struct __begin_fn
{
template < typename R >
constexpr decltype(auto)
operator () ( R && rng ) const
noexcept(noexcept(begin(forward<R>(rng))))
{
return begin(forward<R>(rng)) ;
}
} ;
}
template < typename T >
constexpr T __static_const { } ;
namespace {
auto const & begin =
__static_const< __detail::__begin_fn > ;
}
}
つまり、std::funcはstd名前空間にあるfuncという関数や関数テンプレートではなく、関数オブジェクトになる。その関数オブジェクトは内部でADL経由のオーバーロード解決を行う。単純に__begin_fnの変数を宣言していないのは、ODRを回避するためだ。変数テンプレートにすることで外部リンケージを持ち、そのリファレンスを取ることですべての翻訳単位で共通にする。かつ、無名名前空間で囲むことによってその翻訳単位だけにする。
論文では、最適化の結果、出力されたコードにオーバーヘッドは一切ないことを確認したと書いてある。非最適化コンパイルではオーバーヘッドが生ずるが、問題ないコストであるという。
ただし、論文にも挙げられているように、問題も多少あるので、まだ議論が必要そうだ。
論文は、既存のcustomization pointには互換性のためにこの技法を適用しないとしている。そういう不一致もどうかと思う。
この手法は、range-v3ライブラリで試されているという。
N4382: Working Draft, C++ extensions for Ranges
イテレーターをさらに高級にしたレンジというライブラリの提案。提案中の軽量コンセプトを使って作られている。番兵の概念も導入するらしい。
N4383: C++ Standard Library Active Issues List (Revision R92)
N4384: C++ Standard Library Defect Report List (Revision R92)
C++ Standard Library Closed Issues List (Revision R92)
標準ライブラリの既知の問題、解決済みの問題、議論の結果却下された問題の一覧。
[極めて無駄なPDF] N4386: Unspecialized std::tuple_size should be defined
std::tuple_sizeのプライマリーテンプレートは従来未定義だったが、これを定義する提案。これにより、enable_ifのようなSFINAEの文脈で使いやすくなる。
N4387: Improving pair and tuple, revision 3
pairとtupleのコンストラクターがexplicitとなっているために、一部の初期化ができない問題を修正する提案。
以下のコードはエラーになる。
std::tuple<int, int> pixel_coordinates()
{
return {10, -15}; // エラー、なんで?
}
// コピーできない型、直接初期化はできる。
struct NonCopyable { NonCopyable(int); NonCopyable(const NonCopyable&) = delete; };
std::pair<NonCopyable, double> pmd{42, 3.14}; // エラー、なんで?
pairとtupleが設計されていた当時、暗黙に型変換できない型から暗黙に構築できてしまうことを防ごうとした。
また、C++03時代の0というnullポインター定数の挙動の互換性を保とうという設計もされていた。
また当時、pairはアロケーターサポートのためにコンストラクターの数が膨れ上がっており、新しくコンストラクターを追加するのはできない雰囲気であった。
結果として、N3240提案を受け入れた結果、pairとtupleは以下のような設計がなされることになった。
1. 要素の型が実引数の型から暗黙に変換できない場合は弾く。
struct B { explicit B(bool); };
std::tuple<B> tb = std::tuple<bool>(); // エラー
非テンプレートとテンプレート版の同等のコンストラクター、tuple( const Type & ... )は、explicitとなった。これにより、要素がひとつだけのtupleが、explicitしかコンストラクターがない実引数からコピー初期化されるのを防ぐ
struct X { X(); explicit X(const X&); } x;
std::tuple<X> tx = x; // エラー
struct E { explicit E(int); };
std::tuple<E> te = 42; // エラー
非テンプレート版のコンストラクターは、nullポインター定数である0からの変換を許容する。
class C;
std::tuple<int*> tpi(0); // OK
std::tuple<int C::*> tpmi(0); // OK
この提案は、実装例として、perfect initializationと称する技法を提示している。テンプレートのexplicit/非explicitのコピー風コンストラクターをオーバーロードすることで元の型とほぼ同じように初期化できるという仕組みだ。文面はこの技法を使うことを必須としておらず、実装上の自由をもたせている。
N4388: A Proposal to Add a Const-Propagating Wrapper to the Standard Library
メンバー関数のconst性をポインター風のメンバーに対しても伝播するライブラリ、propagate_constの提案。
非staticメンバー関数のconst修飾は、ポインター風のデータメンバーを伝播しない。
struct A
{
void f() { } // 非const
void f() const { } // const
} ;
struct B
{
std::unique_ptr<A> ptr ;
B() : ptr( std::make_unique<A>() ) { }
void f()
{
ptr->f() ; // 非const版が呼ばれる
}
void f() const
{
ptr->f() ; // 非const版が呼ばれる
}
} ;
これは言語的には正しい挙動だが、意味上のconstとして、ポインターを経由したアクセスでもconst版のメンバー関数が呼ばれて欲しい場合がある。この時に使える。propagate_constライブラリを提案している。名前通りに、const性を伝播させる。
struct B
{
std::propagate_cosnt<std::unique_ptr<A>> ptr ;
// 以下同じ
} ;
N4389: Wording for bool_constant, revision 1
std::integral_constantのbool版のエイリアステンプレート、bool_constantの提案。既存のtrue_typeとfalse_typeはbool_constantを使ったものに書き変える。
namespace std {
// 20.10.3, helper class:
template <class T, T v> struct integral_constant;
// N4386提案
template <bool B>
using bool_constant = integral_constant<bool, B>;
// C++14の定義
// typedef integral_constant<bool, true> true_type;
// typedef integral_constant<bool, false> false_type;
// N4386提案
typedef bool_constant<true> true_type;
typedef bool_constant<false> false_type;
ドワンゴ広告
今週末は勉強会だ。今回は初心者に発表経験を持たせるために、初心者ばかりを募ったはずなので、それほど怖い話はないはずだ。たぶん。
歌舞伎座.tech#8「C++初心者会」 - connpass
ドワンゴは本物のC++プログラマーを募集しています。
CC BY-ND 4.0: Creative Commons — Attribution-NoDerivatives 4.0 International — CC BY-ND 4.0