2017-11-24 30 views
3

私はそれが簡単かつ簡単です使用して、静的なサイズの配列へのエイリアスを持っている:静的サイズの配列型をコンテナ型にできないのはなぜですか?

using triplet_t = std::uint8_t[3]; 

//   vvvvvvvvvvvvvvvvvv <--- easier than std::uint8_t(&triplet)[3] 
void f(const triplet_t &triplet) { /* whatever */ } 

triplet_t t{}; // As good as std::uint8_t t[3]{}; 

t[0] = '0'; 
t[1] = '1'; 
t[2] = '2'; 
for (auto &v : t) std::cout << v << ' '; 
std::cout << '\n'; 

// So far so good... 
triplet_t t3[3]{}; 
for (auto &r : t3) 
    for(auto &v : r) 
     v = 42; 

私もコンテナ内のエイリアスを使用することができます

std::vector<triplet_t> vt; 

それとも私が思うのに使用し、すぐにvtを使用すると、それが失敗した理由:

vt.push_back({}); 

GCC 8.0.0 201711

error: parenthesized initializer in array new [-fpermissive] 
{ ::new((void *)__p) _Up(std::forward<_Args>(__args)...); } 
    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 

error: request for member '~unsigned char [3]' in '* __p', which is of non-class type 'unsigned char [3]' 
destroy(_Up* __p) { __p->~_Up(); } 
        ~~~~~~^~~ 

問題は、すべてのテンプレート策略をアンロールした後、配置、新しいは、括弧で囲まれて、すべてのパラメータを転送して呼び出されることのようだ、と明らかにこれは静的なサイズの配列を初期化する方法ではありません。

また、何とかコンテナはオブジェクトとして考えて、デストラクタを求めています。コンパイルに失敗しました。

std::vector<std::uint8_t[3]> vt; 
vt.push_back({});   // Boom! 
vt.push_back({255, 0, 0}); // Ouch! 

しかし、同じメモリレイアウトとstruct使用しても問題ありません:問題が明らかにエイリアスなし同じである私は、これはなぜ起こるか疑問に思う

struct rgb { std::uint8_t r, g, b; }; 
std::vector<rgb> vt; 
vt.push_back({});   // Nice! 
vt.push_back({255, 0, 0}); // Cool! 

を、静電気にを使用する方法がありますコンテナに格納された型のサイズの配列

+6

_ "静的サイズの配列をコンテナに格納された型として使用する方法はありますか?" _ std :: array'を使用して動作する必要があります。 – user0042

+5

C配列はコピーできません。 – Jarod42

+0

'triplet_t = std :: array 'を使っても同じ結果が得られますか? – dasblinkenlight

答えて

4

push_backCopyInsertable又はMoveInsertableいずれかに値型を必要とします。さんはdefinitionを見てみましょう:

型Tは、そのVALUE_TYPEで同じにTはXにMoveInsertableあるT 場合、および、[指定したコンテナXにCopyInsertableです...以下の式は整形式である:

std::allocator_traits<A>::construct(m, p, v); 

だからCアレイの場合には、一方が有する、標準アロケータため、発現like

::new((void *)p) int[3](std::forward<int[3]>(v)) 
Vがである

配列の型これはspecification of newによれば、病気に形成されている:

  • 型が配列型である場合、オブジェクトの配列が初期化されます。
    • 初期化子がない場合、各要素は初期化子が括弧の空のペアの場合、各要素が値に初期化され、デフォルト初期化
    • あります。
    • initializerが中括弧で囲まれた引数のリストである場合、配列は集約初期化されます。

非空の括弧を可能に配列型のための構文はありません。

MoveInsertableカテゴリの引数は非常に似ています。 全体として、提案された解決策は、std::arrayの(既に言及した)使用であり、それ自体は配列型ではないため、標準的な割り当て子 'construct関数によって取られた構文によって適切に初期化できます。

最終的な注記:厳密なエイリアシング規則では、unsigned char,signed charまたはcharのいずれかのタイプの変換のみが許可されます。 std::uint8_tは、あなたの実装にあるもののうちの1つのエイリアスtypedefに過ぎないことはほぼ確実ですが、標準ではこれが保証されていません。

5

std::vector documentaionを読むと、TCopyAssignableとCopyConstructibleの要件を満たしている必要があります。

vtの2つのインスタンスがTの場合、t = vは有効である必要があります。明らかに、Tがネイティブ配列の場合、そうではなく(別の配列にC配列を割り当てることはできません)、std::vector<T>という特定の関数は不正な形式になります。

溶液としてtriplet_tを定義するであろう:documentationによれば

using triplet_t = std::array<std::uint8_t, 3>; 

void f(const triplet_t &triplet) { /* whatever */ } 

triplet_t t{}; 

t[0] = '0'; 
t[1] = '1'; 
t[2] = '2'; 
for (auto &v : t) std::cout << v << ' '; 
std::cout << '\n'; 

// So far so good... 
triplet_t t3[3]{}; 
for (auto &r : t3) 
    for(auto &v : r) 
     v = 42; 

std::vector<triplet_t> vt; 

vt.push_back({}); 
+0

実際、push_backでは、タイプが 'CopyAssignable'ではなく' CopyInsertable'または 'MoveInsertable'である必要があります。 – Jodocus

+0

@Jodocus UBが呼び出されると、すべて鼻の悪魔の壮大なスキームで同じです;)しかし、私はあなたの答えが良いことに同意します。私はそれをupvoteよ;) – YSC

+0

それは実際にUBではありません。もっと丁寧に投稿したリンクを読むと、C++ 11以降、 'vector'はその型を' CopyAssignable'にする必要はありません。厳密なルールは緩和されました。つまり、タイプ要件は、コンテナではなく実際に実行された操作によって決定されます。 – Jodocus

関連する問題