2012-12-23 17 views
25

C++ 11では、すべてのコンテナの値の型がCopyConstructibleおよびAssignable(コンテナの特定の操作によってこれらの要件を課すことがありますが)であるという要件が削除されました。理論的には、これにより、たとえばstd::deque<const Foo>を定義することが可能になります。これはC++ 03では不可能でした。「割り当て可能」とはどういう意味ですか?

私はこれを試したときに、予期せぬことにgcc 4.7.2が理解できないエラー[1]を吐き出しましたが、少なくともエラーを読みやすくし、libC++でclangをコンパイルしてエラーなしでコンパイルしました。

2つの異なるコンパイラが異なる結果を出すとき、私は正しい答えが何であるか疑問に思うので、const/assignable/value types/containersなどにあるすべての参照を検索します。 Gnu buganizerをはじめとするC++のメーリングリストの中には、SOに関するものや、他の場所にあるものがあります。基本的には以下のような対話として要約することができます。

Q:なぜ私は(簡単な例として)std::vector<const int>を宣言することはできません

A:なぜ地球上であなたがそれをやりたいのでしょうか?それは無意味です。

Q:まあ、それは私には意味があります。なぜ私はそれをすることができないのですか?

A:標準では、値タイプを割り当てる必要があるためです。

質問:私はそれらを割り当てることを予定していません。私はそれらを作成した後、それらをconstにして欲しいです。

A:これは動作しません。次の問題!軽度の破線で

A2:C++ 11はそれを許可することを決定しました。あなたはただ待たなければなりません。その間、あなたのばかげたデザインを考え直してください。

これらは非常に説得力のある回答のようには見えませんが、おそらく私は「私には意味があります」というカテゴリに入るので、私は偏っています。私の場合は、スタックにプッシュされたものがポップされるまで不変であるというようなスタック状のコンテナを用意したいと思います。これは、タイプで表現できるようにするためには特に奇妙なことですシステム。

とにかく、「標準では、すべてのコンテナの値のタイプを割り当てる必要があります」という考えを開始しました。そして、私が見る限り、C++ 03標準のドラフトの古いコピーを見つけたので、それは本当です。それはしました。

一方、std::mapの値のタイプはstd::pair<const Key, T>です。割り当て可能なようには見えません。それでも、私はstd::deque<std::tuple<const Foo>>でもう一度試しました。そしてgccは目を打たずにコンパイルを進めました。だから私はいくつかの回避策があります。

は、その後、私はstd::is_assignable<const Foo, const Foo>std::is_assignable<std::tuple<const Foo>, const std::tuple<const Foo>>の値をプリントアウトしようとした、そしてあなたが期待するとして、それは、前者が割り当てられないと報告されていることが判明したが、後者は(打ち鳴らすとgccの両方によって)割り当て可能として報告されます。もちろん、それは本当に割り当て可能ではありません。 a = b;をコンパイルしようとするとgccは苦情error: assignment of read-only locationで拒否されました(これは、このクエストで実際に分かりやすい唯一のエラーメッセージです)。しかし、割り当てを試みることなく、clangとgccの両方がdeque<const>をインスタンス化するのと同じくらいうれしく、コードは正常に動作しているようです。

std::tuple<const int>が本当に割り当て可能であれば、私はC++03標準の矛盾については不平を言うことはできません - 本当に誰が気にしていますか?しかし、2つの異なる標準ライブラリの実装では、実際に、その参照を代入しようとすると(非常に賢明な)コンパイラエラーにつながる場合は、割り当て可能です。私はある時点でSFINAEのテンプレートでテストを使用したいと思うかもしれませんが、今日見たものに基づいて、それは非常に信頼できるものではありません。

質問に質問を出すことができる人がいます(タイトル内):割り当て可能とはどういう意味ですか?そして、2つのボーナス質問:

1)委員会は、本当にconst値の型を持つコンテナをインスタンス化できるように、もしかして、または彼らは心の中でいくつかの他の非割り当て可能なケースがありましたか?、

2)は、実際にありますconst Foostd::tuple<const Foo>の間の重要な違いはありますか?


[1]本当に好奇心のために、ここでは(いくつかの行末を追加して、そしてあなたが十分スクロールダウンした場合の説明)std::deque<const std::string>の宣言をコンパイルしようとしたときに、GCCによって生成されるエラーメッセージです。

In file included from /usr/include/x86_64-linux-gnu/c++/4.7/./bits/c++allocator.h:34:0, 
       from /usr/include/c++/4.7/bits/allocator.h:48, 
       from /usr/include/c++/4.7/string:43, 
       from /usr/include/c++/4.7/random:41, 
       from /usr/include/c++/4.7/bits/stl_algo.h:67, 
       from /usr/include/c++/4.7/algorithm:63, 
       from const_stack.cc:1: 
/usr/include/c++/4.7/ext/new_allocator.h: In instantiation of ‘class __gnu_cxx::new_allocator<const std::basic_string<char> >’: 
/usr/include/c++/4.7/bits/allocator.h:89:11: required from ‘class std::allocator<const std::basic_string<char> >’ 
/usr/include/c++/4.7/bits/stl_deque.h:489:61: required from ‘class std::_Deque_base<const std::basic_string<char>, std::allocator<const std::basic_string<char> > >’ 
/usr/include/c++/4.7/bits/stl_deque.h:728:11: required from ‘class std::deque<const std::basic_string<char> >’ 
const_stack.cc:112:27: required from here 
/usr/include/c++/4.7/ext/new_allocator.h:83:7: 
    error: ‘const _Tp* __gnu_cxx::new_allocator< <template-parameter-1-1> >::address(
    __gnu_cxx::new_allocator< <template-parameter-1-1> >::const_reference) const [ 
    with _Tp = const std::basic_string<char>; 
    __gnu_cxx::new_allocator< <template-parameter-1-1> >::const_pointer = 
    const std::basic_string<char>*; 
    __gnu_cxx::new_allocator< <template-parameter-1-1> >::const_reference = 
    const std::basic_string<char>&]’ cannot be overloaded 
/usr/include/c++/4.7/ext/new_allocator.h:79:7: 
    error: with ‘_Tp* __gnu_cxx::new_allocator< <template-parameter-1-1> >::address(
    __gnu_cxx::new_allocator< <template-parameter-1-1> >::reference) const [ 
    with _Tp = const std::basic_string<char>; 
    __gnu_cxx::new_allocator< <template-parameter-1-1> >::pointer = const std::basic_string<char>*; 
    __gnu_cxx::new_allocator< <template-parameter-1-1> >::reference = const std::basic_string<char>&]’ 

それでは、ここで起こっているのは、標準(§ 20.6.9.1)は、デフォルトのアロケータは、メンバ関数を持っていると主張しているということです。

pointer address(reference x) const noexcept; 
const_pointer address(const_reference x) const noexcept; 

constテンプレート引数(明らかにUB)でインスタンス化すると、referenceconst_referenceは同じ型であり、宣言が複製されます。定義の本体は、それが価値があるものと同じです。したがって、アロケータを認識できるコンテナは明示的にconstの値の型を扱うことはできません。 consttupleの内部に隠すと、アロケータがインスタンス化できます。標準からのこのアロケータ要件は、std::vector<const int>の問題に関する少なくとも2つの古いlibstdC++バグをクローズするのを正当化するために使用されましたが、原理の固い点として私を攻撃しません。また、libC++は、重複した関数宣言を削除してallocator<const T>の特殊化を提供するという、明らかな簡単な方法で問題を回避します。

C++ 03
+2

IIRC割り当て可能な要件は削除されず、2つに分割されます(コピーおよび移動用) –

+0

問題は、 'std :: deque'(またはシーケンス一般的にはコンテナ)ですが、 'std :: allocator'によって?代入可能な意味ではなく、 'const'型で動作するアロケータを使用しても問題ありません(' std :: allocator'は 'const'型では動作しません)? – jogojapan

+0

@BenVoigt:それは本当になくなっています。以前は23.1(3)でした。「これらのコンポーネントに格納されているオブジェクトのタイプは、CopyConstructibleタイプ(20.1.3)の要件と、割り当て可能タイプの追加要件を満たす必要があります。その段落、次の段落、および関連するテーブル64(Assignableを参照)はすべて消えています。残りの要件は、コンテナでコピーコンストラクタまたはコピー代入を使用する場合、値タイプTがコンテナタイプXにCopyInsertableでなければならないということです。 – rici

答えて

12

Assignableが一方

 
    Expression Return type Post-condition 
    t = u   T&    t is equivalent to u 

、§23.1/ 4のテーブル64によって定義された。この要件はstd::map満たしませんでした。他方、それはstd::listのためにはあまりにも厳しい要件でした。また、C++ 11では、一般的にstd::vectorでは必要ではないが、特定の操作(割り当てなど)の使用によって課されることが示されています。 C++ 11の対応する要求に

は、CopyAssignableと命名されており、主な違いは、コンテナ要素がもはやCopyAssignableをする必要があることではない§17.6.3.1/ 2、

 
    Expression Return type Return value Post-condition 
    t = v   T&    t    t is equivalent to v, the 
               value of v is unchanged 

テーブル23によって定義されます。対応する要件があることを確認してくださいMoveAssignable

とにかく、constデータメンバーを持つ構造体は、非常に特殊な解釈で”に相当する“を読み取ることを選択しない限り、明確に割り当てられません。

C++ 11での操作に依存しない要素型の要件は、§23.2.1/ 4の表96からわかるとおり、Destructibleでなければなりません。


std::is_assignableに関しては、それは非常にCopyAssignable基準をテストするものではありません。

ここ’ C++ 11§20.9.4.3/ 3のテーブル49によれば、std::is_assignable<T, U>が示すもの(S)

“未評価のオペランドとして を処理すると式 declval<T>() = declval<U>()を整形 あります (第5項)。アクセス T およびUとは無関係に、 のようにチェックが実行されます。妥当性 の即時コンテキスト の代入式 が考慮されます。 【注: 発現の コンパイルは、 クラス テンプレート特殊 のインスタンスと関数テンプレート 特殊、 暗黙的に定義 関数の 生成などとして 副作用をもたらすことができます。このような の副作用は、「即時のコンテキスト」の になく、 のプログラムでは、 が不正な形式になる可能性があります。 末端 ノート] ”

は、基本的にこれはアクセス/存在+引数型の互換性のoperator=のチェック、そしてより多くの何も意味します。

ただし、Visual C++ 11.0グラム++ 4.7.1がそれにチョークながら、アクセスチェックを行うには表示されません。

#include <iostream> 
#include <type_traits> 
#include <tuple> 
using namespace std; 

struct A {}; 
struct B { private: B& operator=(B const&); }; 

template< class Type > 
bool isAssignable() { return is_assignable< Type, Type >::value; } 

int main() 
{ 
    wcout << boolalpha; 
    wcout << isAssignable<A>() << endl;    // OK. 
    wcout << isAssignable<B>() << endl;    // Uh oh. 
} 

ビルのVisual C++ 11.0と:

 
[D:\dev\test\so\assignable] 
>cl assignable.cpp 
assignable.cpp 

[D:\dev\test\so\assignable] 
> _ 

ビルでG ++ 4.7.1:だから

 
[D:\dev\test\so\assignable] 
>g++ assignable.cpp 
d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/type_traits: In substitution of 'template static decltype (((declval)()=(declval)(), std::__sfinae_types::__one()))std::__is_assignable_helper::__test(int) [with _ 
Tp1 = _Tp1; _Up1 = _Up1; _Tp = B; _Up = B] [with _Tp1 = B; _Up1 = B]': 
d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/type_traits:1055:68: required from 'constexpr const bool std::__is_assignable_helper::value' 
d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/type_traits:1060:12: required from 'struct std::is_assignable' 
assignable.cpp:10:59: required from 'bool isAssignable() [with Type = B]' 
assignable.cpp:16:32: required from here 
assignable.cpp:7:24: error: 'B& B::operator=(const B&)' is private 
In file included from d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/bits/move.h:57:0, 
       from d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/bits/stl_pair.h:61, 
       from d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/bits/stl_algobase.h:65, 
       from d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/bits/char_traits.h:41, 
       from d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/ios:41, 
       from d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/ostream:40, 
       from d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/iostream:40, 
       from assignable.cpp:1: 
d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/type_traits:1049:2: error: within this context 
d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/type_traits: In substitution of 'template static decltype (((declval)()=(declval)(), std::__sfinae_types::__one())) std::__is_assignable_helper::__test(int) [with _ 
Tp1 = _Tp1; _Up1 = _Up1; _Tp = B; _Up = B] [with _Tp1 = B; _Up1 = B]': 
d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/type_traits:1055:68: required from 'constexpr const bool std::__is_assignable_helper::value' 
d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/type_traits:1060:12: required from 'struct std::is_assignable' 
assignable.cpp:10:59: required from 'bool isAssignable() [with Type = B]' 
assignable.cpp:16:32: required from here 
assignable.cpp:7:24: error: 'B& B::operator=(const B&)' is private 
In file included from d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/bits/move.h:57:0, 
       from d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/bits/stl_pair.h:61, 
       from d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/bits/stl_algobase.h:65, 
       from d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/bits/char_traits.h:41, 
       from d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/ios:41, 
       from d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/ostream:40, 
       from d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/iostream:40, 
       from assignable.cpp:1: 
d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/type_traits:1049:2: error: within this context 
d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/type_traits: In instantiation of 'constexpr const bool std::__is_assignable_helper::value': 
d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/type_traits:1060:12: required from 'struct std::is_assignable' 
assignable.cpp:10:59: required from 'bool isAssignable() [with Type = B]' 
assignable.cpp:16:32: required from here 
assignable.cpp:7:24: error: 'B& B::operator=(const B&)' is private 
In file included from d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/bits/move.h:57:0, 
       from d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/bits/stl_pair.h:61, 
       from d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/bits/stl_algobase.h:65, 
       from d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/bits/char_traits.h:41, 
       from d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/ios:41, 
       from d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/ostream:40, 
       from d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/iostream:40, 
       from assignable.cpp:1: 
d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/type_traits:1055:68: error: within this context 

[D:\dev\test\so\assignable] 
> _ 

、合算し、標準のstd::is_assignableは非常にLiがあるように思われますこの記事の執筆時点では、移植可能なコードに依存することはできません。


EDIT:は正しい<type_traits<utility>を置き換え。興味深いことに、g ++では重要ではありませんでした。エラーメッセージでさえそうではないので、私はそれをそのままにしておきます。

+0

明らかにすると、両方の標準ライブラリに対してバグを提出する必要があります。 – rici

+0

@rici:いいえ、タプルの 'true'を報告する理由はありません。明らかに' CopyAssignable'ではなく 'std :: is_assignable'の要件を満たしているようです。私の答えの2番目のセクションを参照してください(私はそれを追加しました)。しかし、少なくとも*いくつかの実装はバグですが、それは同意しないからです! –

+2

FWIWあなたのプログラムは、今後のGCC 4.8のスナップショットで受け入れられ、予期した結果が得られます(つまり、 'true'から 'false'を出力する)。 –

4

私はAlfにこれを与えていますが、私は後で参照するために2つのメモを追加したいと思います。

Alfが言うように、std::is_*_assignableは実際には、適切な代入演算子の存在(明示的または暗黙的)のみをチェックします。インスタンス化されていれば、それが整形式になるかどうかを必ずしも確認する必要はありません。これはデフォルトの代入演算子でうまく動作します。 constと宣言されたメンバーがある場合、デフォルトの代入演算子は削除されます。基本クラスに削除された代入演算子がある場合、デフォルト代入演算子は削除されます。だから、デフォルトを自分たちのことに任せれば、うまくいくはずです。

ただし、operator=と宣言すると、適切に削除されるようにするのはあなたの責任となります(気にする場合)。例えば、これは、コンパイルし、実行(少なくとも打ち鳴らすと)、およびis_assignable Cという報告:代入演算子の定義が存在しない場合は、リンク時まで指摘されていない

#include <iostream> 
#include <type_traits> 
#include <tuple> 
using namespace std; 

struct A { const int x; A() : x() {}}; 

struct C { 
    struct A a; 
    C& operator=(C const& other); 
}; 

template< class Type > 
bool isAssignable() { return is_assignable< Type&, const Type& >::value; } 

int main() 
{ 
    wcout << boolalpha; 
    wcout << isAssignable<A>() << endl; // false 
    wcout << isAssignable<C>() << endl; // true 
    C c1; 
    C c2; 
} 

、とではない。この場合、代入演算子は一度も使用されないためです。しかし、を使用してstd::is_assignableを条件にコンパイルすることが許可されることに注意してください。もちろん、C::operator=をメンバーaに割り当てられた形で定義することはできませんでした。そのメンバーは割り当て不可能なためです。

これは特に興味深い例ではありません。興味深いのは、この全体の質問を開始したstd::tuple号のようなテンプレートの使用です。のは、そのメンバーaに割り当てて上記にテンプレートのカップルを追加し、実際にC::operator=を定義してみましょう:最後に割り当てなければ

using namespace std; 

template<bool> struct A { 
    A() : x() {} 
    const int x; 
}; 

template<bool B> struct C { 
    struct A<B> a; 
    C& operator=(C const& other) { 
    this->a = other.a; 
    return *this; 
    } 
}; 

template< class Type > 
bool isAssignable() { return is_assignable< Type&, const Type& >::value; } 

int main() 
{ 
    wcout << boolalpha; 
    wcout << isAssignable< A<false> >() << endl; // false 
    wcout << isAssignable< C<false> >() << endl; // true 
    C<false> c1; 
    C<false> c2; 
    c1 = c2;          // Bang 
    return 0; 
} 

、コードがコンパイルされ、実行され、A<false>があるという報告(打ち鳴らす3.3下)割り当て可能ではありません(正しい)が、C<false>は割り当て可能です(驚きです)。 C::operator=を実際に使用しようとすると、真実が明らかになる。なぜなら、コンパイラが実際にその演算子をインスタンス化しようとするまではないからである。それまでは、is_assignableのインスタンス化によって、演算子はパブリックインターフェイスの宣言に過ぎませんでした.Alfが述べているように、すべてstd::is_assignableが本当に探しています。

Phew。

私はこれは、コンポーネントタイプのいずれかが割り当てられない場合、operator=が削除されるべきである標準集約オブジェクトに関する標準ライブラリ実装と標準ライブラリ実装の両方に欠点があると考えています。 std::tupleの場合、§ 20.4.2.2には、すべてのコンポーネントタイプを割り当てることができるoperator=の要件としてリストされていますが、他のタイプと同様の要件がありますが、ライブラリ実装者に適用外のoperator=を削除する必要はありません。

しかし、わかっている限り、ライブラリの実装が削除を実行するのを止めるものはありません(代入演算子を条件付きで削除するという煩わしさを除く)。私の意見では、これについて数日後に執着した後、そうするべきであり、さらにその基準はそうするように要求しなければならない。そうでなければ、is_assignableの確実な使用は不可能です。

+0

@LucDantonある:しかし 'のstd ::タプルの場合 'の場合、代入演算子は意味をなさない。私は 'std :: tuple'が明示的に削除しなければならないと思います。その場合、 'std :: is_assignable >'は、 'is_assignable'のポイントである' false_type'とPOLSを満足するでしょう。割り当て可能性が(いくつかの)テンプレート引数の割り当て可能性に左右されるすべてのテンプレート化オブジェクトが、関連するテンプレート引数が割り当て可能でない場合に割り当て演算子を削除することであった場合、装置全体が正しく動作する。 – rici

+0

@LucDanton、余白は小さすぎます:)。私が譲渡可能性のためにテンプレートパラメータをテストしようとするなら、他の人がそうしている限りうまくいくでしょう。私は自分でそれをすることはできません。さらに、割り当てができないインスタンス化の代入演算子を正しく削除すると、テンプレートインスタンス化トレースバックのページをトレースバックするのではなく、わかりやすいエラーメッセージ(代入演算子が削除されます)がクライアントに表示されます。 – rici

+0

タプルが代入演算子を条件付きで提供するように強制されていない理由についての素敵な考察は、通常よりも迷惑をかけることです(より簡単な種類のSFINAEではなく部分的な特殊化)。いずれにしても、現在の状況に関しては、私は実装を責めるのではなく、要件を責めます。 –

関連する問題