2014-04-07

2014-02-post-Issaquah mailingsのレビュー:N3930-N3939

2014-02-post-Issaquah mailingが公開されているので、レビューを続ける。

[title要素が残念すぎる] N3930: C++ Standard Library Immediate Issues

標準ライブラリに持ち上がっている問題の修正案。

特に興味深いものを紹介すると。

2112. User-defined classes that cannot be derived from

あるクラスからの派生を禁止する機能は、finalとしてC++11に入った。

// finalの例
class underivable final { } ;

// ill-formed
class try_derive : underivable { } ;

実は、C++03でも、クラスからの派生を禁止させる方法はあるにはあったが、あまりにもトリッキー過ぎて、それほど一般的に使われていない。

More C++ Idioms/Final Class - Wikibooks, open books for an open world

finalがC++に入ったことで、ユーザー定義型は、簡単に派生を禁止できるようになった。そこで、ひとつ重大な問題が持ち上がった。標準ライブラリに渡す型は、派生禁止を認めるのかどうか、である。

C++の標準ライブラリは、テンプレートを用いて、ユーザーが定義した型を受け取る。標準ライブラリに渡す型には、規程によりいくつかの要件がある。

また、C++には、EBCO(Empty Base Class Optimization)という技法がある。C++では規格上、空のクラスから派生したクラスは、空のクラス分のストレージを無視することが許されている。

// EBCOの例
struct empty { } ;

// EBCOが許されている
struct is_a : empty
{ char c ; } ;

// EBCOの働く余地はない
struct has_a
{
    char c ;
    empty a ;
} ;


上記のコードで、is_aは、empty分のストレージを割り当てるなくてもよいことが、規格上認められている。空の基本クラスに、わざわざ無駄なストレージを割り当てる必要がないということは、、容量効率をよくできる。

このEBCOは、モダンなC++実装では広く使われているし、モダンなSTL実装でも、活用されている。たとえば、unique_ptrを考える。

// EBCOを使わないunique_ptr
template <class T, class D = default_delete<T> >
class unique_ptr
{
    // デリーターをメンバーとして持つ
    deleter_type deleter ;

public :
    deleter_type & get_deleter() noexcept
    {
        // メンバーを返す
        return deleter ;
    }

// 略
} ;

この実装は、deleterが空のクラスであった場合、容量的に非効率である。EBCOを使うと、以下のように書ける。

// EBCOを使うunique_ptr
template <class T, class D = default_delete<T> >
class unique_ptr : D // デリーターから派生する
{
public :
    deleter_type & get_deleter() noexcept
    {
        // 自分はデリーターから派生している
        return *this ;
    }
} ;

このように、自分自身がテンプレート仮引数として与えられるデリーターから派生することで、デリーターが空クラスであった場合にも、容量効率が良くなる。

問題は、C++11で派生を禁止できる機能が入ったことにより、ユーザー定義のデリーターは、finalである可能性がある。これは、EBCOを使っているSTL実装で問題になる。

// EBCOが使えないデリーター
class custom_deleter final
{
    template < typename Pointer >
    void operator () ( Pointer ) cosnt
    {
        // 実装
    }
} ;

// 実装上の問題でill-formedになってしまう
std::unique_ptr< int, custom_deleter > p ;

この問題が指摘された2011年11月30日当時、主要なSTL実装は、EBCOを多用していたので、派生不可能なクラスをテンプレート実引数として渡すと、軒並みコンパイルエラーになってしまった。

また、std::tupleの実装も、派生による主流な実装方法では、動かない。

ではいったいどうするのか。STLに渡す型はfinalであってはならないという要件を追加するのか。しかし、そのような要件を追加してしまうことは、明らかに邪道である。C++的ではない。finalな型も認めなければならない。

この問題は、ある型がfinalであるかどうかを判定できるメタ関数、is_finalがあれば解決する。たとえば、以下のように書ける。

// is_finalがある場合

// EBCO実装
template < typename D, typename = void >
class deleter_holder : public D
{
public :
    D & get_deleter() noexcept
    {
        return *this ;
    }

// コンストラクターなど
} ;

// 非EBCO実装
template < typename D >
class deleter_holder< D, std::enable_if_t< is_final<D> >
{
    D d ;
public :
    D & get_deleter() noexcept
    {
        return d ;
    }

// コンストラクターなど
}

template < typename T, typename D = std::default_deleter >
class unique_ptr : public deleter_holder< D >
{

} ;

このように、簡単なメタプログラミングで対応できる。is_finalのようなコンパイラーマジックが標準化されない場合、ライブラリー実装の移植性がなくなるので、is_finalのようなメタ関数を標準化すべきである

論文の修正案も、is_finalを規格に付け加えるものとなっている。

2132. std::function ambiguity

以下のコードは、オーバーロード解決が曖昧となる。

// オーバーロード解決が曖昧となる例
#include <functional>

void f(std::function<void()>) {}
void f(std::function<void(int)>) {}

int main() {
  f([]{});
  f([](int){});
}

fの呼び出しが曖昧な理由は、std::functionの変換関数が、どのようなstd::functionの特殊化であっても、同一だからである。この問題を解決するには、std::functionの変換関数に与えられた実引数の型が、実際にstd::functionのテンプレート実引数の引数に対して呼び出し可能であるかどうかを調べ、もし呼び出し可能ではない場合、SFINAEなどの技法を使い、候補関数に現れないというメタプログラミングが必要になる。

これを実装するにあたり、呼び出し可能かどうかを調べるには、result_ofだけで十分なのだが、意図を明確にするために、is_callableというメタ関数があったほうがいいのではないかという議論もされたようだ。

最終的には、is_callableは追加しないという方向に向かっている。

2263. Comparing iterators and allocator pointers with different const-character

core issue 179という、大昔の問題が、掘り返されている。

問題は、iterator とconst_iteratorは比較可能であるべきだが、保証する文面が見当たらない。また、pointerとconst_pointerも比較可能であるべきだが、やはり文面による保証が見当たらない、というもの。

コレに対する文面の追加。

その他、些細な文面上の誤りの修正が多い。

N3931: Filesystem Study Group (SG3) Issues Resolved Directly In Issquah

Filesystemのドラフトの文面の小粒な修正集。重箱の隅をつつくような文面の訂正が多い。

例えば、現行文面では、ファイル名の長さは、実装依存(implementation dependent)となっているが、規格ではそのような用語の意味を定義していない。規格で定義している用語には、実装定義(implementation defined)とオペレーティングシステム依存(operating system dependent)がある。ファイル名の長さは、明らかにOS依存であるので、OS依存という用語を使うようにする。などなど。

N3932: Variable Templates For Type Traits (Revision 1)

N3854で提案された、従来の、値を返すtraitsに対する、変数テンプレートのラッパーの、文面案。

たとえば、

constexpr bool b = std::is_same<T, U>::value ;

というコードは、この提案を使えば、

constexpr bool b = std::is_same_v<T, U> ;

と書くことができる。スコープ解決演算子を使う必要がないので、簡潔に書くことができる。命名法則は、従来のtraitsに、_vを追加した名前になる。

実装はとても簡単で、以下の通り。

// N3932の実装例
template < typename T, typename U >
constexpr bool is_same_v = is_same<T, U>::value ;

すでに、型を返すtraitsに対する同様のラッパーは、エイリアステンプレートを使ったものが、C++14に追加されている。

using pointer_type = std::add_pointer<T>::type ;

というコードが、

using pointer_type = std::add_pointer_t<T> ;

このように書ける。コレも、スコープ解決演算子を使う手間を省ける。

この実装も、機械的に簡単だ。

// 実装例
template < typename T >
using add_pointer_t = add_pointer<T>::type ;

N3933, N3934, N3935

[デブいPDF] N3936: Working Draft, Standard for Programming Language C++

現在の最新のC++ドラフト。

本物のC++プログラマーならば、常に最新のドラフトを参照しながらコードを書くはずである。

[もひとつデブいPDF] N3937: Programming Languages — C++

内容はN3936とほぼ同じだが。ISO規格として発行する際の体裁が整えられているドラフト。いよいよC++14発行の日が近い。何事もなければ、今年末に発行される。

N3938: Editor's Report

ドラフト編集者の報告書。前回のドラフトN3797からの変更点を記載している。今回は、git logがついている。

ちなみに、C++ドラフトは、現在GitHubで、ソースコード(tex)が公開されている。

cplusplus/draft

簡単な誤字脱字の修正程度ならば、pull requestも受け付けている。

N3939: Extending make_shared to Support Arrays, Revision 2

std::shared_ptrをネイティブに配列に対応させる提案論文の改訂版。わざわざカスタムデリーターを指定子なくても、shared_ptr<T[]>を認識して、自動的にdelete[]を使ってくれるようになる。

前回のN3870からの変更点は、単一のT &&を受け取るmake_sharedが、議論の結果、取り除かれた。

ドワンゴ広告

この記事は恒例のドワンゴ勤務中に書かれた。

ドワンゴは本物のC++プログラマーを募集しています。

採用情報|株式会社ドワンゴ

CC BY-ND 4.0: Creative Commons — Attribution-NoDerivatives 4.0 International — CC BY-ND 4.0

No comments: