読者です 読者をやめる 読者になる 読者になる

C++0x Placement Insert の問題

C++

怪しいところもありますが、 N2642 の簡単な訳です。
間違ってるところがありましたら指摘してください。


コンストラクタの引数をとる push_back の問題点



・教育的な問題
この可変引数の push_back が

vector<something> v;
v.push_back(a, b, c);

複数の要素を一気に push_back するのか

v.push_back(a);
v.push_back(b);
v.push_back(c);

something のコンストラクタの引数を取るのか

v.push_back(something(a, b, c));

混乱が起きてしまう(実際は後者)

問題は、この混乱が言語を学ぶ上で良くないこと


・explicit の問題
explicit が付いているコンストラクタを暗黙に呼び出しているように見える

struct A {};

struct B {
    explicit B(A);
};


A a;
vector<B> v;

v.push_back(a);    // OK - push_back 内で明示的にコンストラクタが呼ばれる
v.push_back(B(a)); // OK - push_back を呼ぶ側で明示的にコンストラクタが呼ばれる

だが、実際には push_back 内で明示的にコンストラクタを呼び出しているため
explicit の明示性を犯しているわけではない


また、この可変引数 push_back には以下のような用途がある

vector<vector<int>> vv;
vv.push_back(); // push_back 内では vector<int> のデフォルトコンストラクタを呼び出している
vv.back().push_back(1);

C++03 で同じことをやろうとすると以下のようになる

vector<vector<int>> vv;
vv.resize(vv.size() + 1);
vv.back().push_back(1);

これは正常に動作するが、可変引数 push_back よりも混乱させ、
教えることが難しいようである


・0問題

以下は C++03 の合法なコードである

vector<int*> v;
v.push_back(0);


だが、この提案の以降バージョンで以下のようなことが起こる

vector<int*> v;
v.push_back();        // OK : 第1引数はヌルポインタ
v.push_back(nullptr); // OK : 第1引数はヌルポインタ
v.push_back(0);       // エラー! : int* は int で初期化できない

ここで起きることは
・push_back の唯一の signature がテンプレートだということ
・テンプレートによって、push_back の引数が int であると推論されること
・上記理由により、 0 の不思議な性質が消失することである


この問題は、本来の signature を元に戻して、次のようにテンプレートを強制する
組み合わせで解決できる

void push_back(const T& x);

template <class... Args>
requires Constructible<T, Args&&...>
void push_back(Args&&... args);


同じ問題が、pair のコンストラクタでも起こる

pair<char*, char*> p(0, 0); // エラー!

もう一度言うと、この問題は適切な制約と非テンプレート オーバーロードによって解決される


・Emplace オーバーロード

前回の提案(N2345)では、連想コンテナが持つオーバーロードされた2つの insert にマッチする
オーバーロードされた2つの emplace を提供した

value_type とその他のために、ヒントとコンストラクタの引数を、引数にとった

template <class... Args>
a.emplace(Args&&... args);

template <class... Args>
a.emplace(r, Args&&... args); // hint version

このコンストラクタ引数がコンテナの const_iterator と同じだった場合
signature をあいまいにすることができる。

class bar {
    bar();
    bar(set<bar>::const_iterator);
};

set<bar> s;
ser<bar>::const_iterator i = something();
s.emplace(i); // emplace(i, bar());

これは hint version を呼ぶ
そしてそれはおそらくあなたが望むものではないだろう

このケースはかなり稀な例だが、この問題に出くわした場合
デバッグするのが難しいだろう


単純な回避策がある

s.emplace(s.begin(), i);

これを解決策として提案したが、これには2つの問題があることがわかった
1.それを回避するにはこの問題を知っている必要がある
2.begin() が原因でパフォーマンスを落とす




・保守的なアプローチ
上記のような問題の単純な解決方法は、全ての関数に別々の名前を付けることである

たとえば list に

template <class... Args>
void emplace_front(Args&& args);

template <class... Args>
void emplace_back(Args&& args);

template <class... Args>
iterator emplace(const_iterator position, Args&&... args);

それと set に

template <class... Args>
pair<iterator, bool> emplace(Args... args);

template <class... Args>
iterator emplace_hint(const_iterator position, Args&&... args);

この方法だと、新しい別々の関数になるので教育的な問題はなくなる

explicit 問題は、emplace はそういう形式のオブジェクト構築を行うような設計になるので
explicit コンストラクタによる予想外のオブジェクト構築を防ぐことができる
(かなり怪しい翻訳…)

nullptr がヌルポインタを指定する唯一の合法な方法だと指示することができるので 0 問題はなくなる

ヒント版は異なる名前なので、オーバーロードの問題はなくなる



・曲芸的なアプローチ

vector<something> v;
v.push_back(a);             // a によるコピーコンストラクト
v.push_back(emplace(b, c)); // b, c による配置コンストラクト
v.insert(p, a);             // a によるコピーコンストラクト
v.insert(p, emplace(b, c)); // b, c による配置コンストラクト

このアプローチはいくつかの優れたプロパティをもつ
1つは、新しい名前の関数が必要ないこと
2つめは、オーバーロードがあいまいではないこと
3つめは、map のキー値の配置コンストラクトを許すオーバーロードを提供することができること
そして何よりも非常に単純で使いやすい

唯一の問題は、実装がかなり巧妙であるということである
ある種のタプルでメタプログラミングを必要とする
そして、それはコンストラクタにパラメータを渡す際のオーバーヘッドを避けなければならない

実際、これが実現可能かどうかよくわからない



・最終的な決定が終わるまでの提案

Pair

// Pair
template <class U, class... Args>
requires Constructible<T1, U&&> && Constructible<T2, Args&&...>
pair(U&& x, Args&&... args);

template <class U, class... Args>
requires Constructible<T, Args&&...>
pair(allocator_arg_t, const Alloc& a, U&& x, Args&&... args);


シーケンス

// Change
void push_back(const T& x);
void push_back(T&& x);

// Add
template <class... Args>
emplace_front(Args&&... args);

template <class... Args>
emplace_back(Args&&... args);


連想コンテナ

template <class... Args>
emplace(Args&&... args);

template <class... Args>
emplace_hint(r, Args&&... args);


Unordered 連想コンテナ

template <class... Args>
emplace(Args&&... args);

template <class... Args>
emplace_hint(r, Args&&... args);


シーケンスコンテナのコンテナアダプタ

template <class... Args>
emplace(Args&&... args)
{ c.emplace_back(forward<Args>(args)...); }


N2642 Proposed Wording for Placement Insert