C配列の初期化「int arr [] = {e1、e2、e3、…}」の動作をstd :: arrayでエミュレートする方法は?


137

(注:この質問は、要素の数を指定する必要はなく、ネストされた型を直接初期化できるようにする必要があります。)
この質問では、のようなC配列の残りの使用について説明しint arr[20];ます で彼の答え、@James観世ショーC配列の最後の砦の一つ、それはユニークな初期特性です:

int arr[] = { 1, 3, 3, 7, 0, 4, 2, 0, 3, 1, 4, 1, 5, 9 };

要素の数を指定する必要はありません。ここで、C ++ 11関数std::beginstd::endfrom <iterator>または独自のバリアント)を使用して反復処理します。サイズについて考える必要はありません。

今、同じことを達成する(おそらくTMP)方法はありますstd::arrayか?見栄えを良くするために許可されたマクロの使用。:)

??? std_array = { "here", "be", "elements" };

編集:さまざまな回答からコンパイルされた中間バージョンは、次のようになります。

#include <array>
#include <utility>

template<class T, class... Tail, class Elem = typename std::decay<T>::type>
std::array<Elem,1+sizeof...(Tail)> make_array(T&& head, Tail&&... values)
{
  return { std::forward<T>(head), std::forward<Tail>(values)... };
}

// in code
auto std_array = make_array(1,2,3,4,5);

そして、あらゆる種類のクールなC ++ 11ものを採用しています:

  • 可変テンプレート
  • sizeof...
  • 右辺値参照
  • 完璧な転送
  • std::array、 もちろん
  • 均一な初期化
  • 均一な初期化で戻り値の型を省略
  • 型推論(auto

そして例はここにあります

ただし、@ Xaadeの回答のコメントで@Johannesが指摘しているように、そのような関数を使用してネストされた型を初期化することはできません。例:

struct A{ int a; int b; };

// C syntax
A arr[] = { {1,2}, {3,4} };
// using std::array
??? std_array = { {1,2}, {3,4} };

また、初期化子の数は、実装でサポートされている関数とテンプレート引数の数に制限されます。


可変メソッド。それは初期化ではなく、割り当てのようなものですが、私が到達できる最も近いものです。初期化するには、メモリに直接アクセスする必要があります。
Lee Louviere、

どうやらC ++ 0xは初期化構文をサポートしています。驚くばかり。それは、より複雑なサポートのための言語サポートを備えたC#のようになることのようなものです。インターフェイスの正式な言語サポートが提供されているかどうかは誰でも知っていますか???
Lee Louviere、

10
@Downvoter:理由は?
Xeo

1
謝罪、TMPあなたの質問の意味は何ですか?
kevinarpe

1
@kevinarpe TMPはおそらくテンプレートのメタプログラミングを表しています。
BeeOnRope

回答:


63

私が考えることができる最高は:

template<class T, class... Tail>
auto make_array(T head, Tail... tail) -> std::array<T, 1 + sizeof...(Tail)>
{
     std::array<T, 1 + sizeof...(Tail)> a = { head, tail ... };
     return a;
}

auto a = make_array(1, 2, 3);

ただし、これにはコンパイラがNRVOを実行し、戻り値のコピーをスキップする必要があります(これも合法ですが必須ではありません)。実際には、どのC ++コンパイラでも、直接初期化と同じくらい高速になるように最適化できると期待しています。


gcc 4.6.0は2番目のものをコンパイルせず、doubleからvalue_typeへの変換のナローイングについて不平を言っていますが、clang ++ 2.9は両方で問題ありません!
Cubbi

20
このような答えがあれば、Bjarneが「新しい言語のように」と感じたことについてほとんど理解できます。
Matthieu M.11年

@Matthieu:次に、@ DeadMGのコードから右辺値参照、完全な転送、均一な初期化を追加すると、多くの新機能が設定されます。:>
Xeo

1
@Cubbi:実際には、g ++がここにあります-C ++ 0xでの集約初期化ではナロー変換は許可されていません(ただし、C ++ 03では許可されています-気付かなかった重大な変更です!)。2番目のmake_array通話を削除します。
Pavel Minaev、

@Cubbi、そうですが、それは明示的な変換です-サイレントダウンキャストやその他のことも許可static_assertします。これTailT、とを使用して暗黙的にに変換できないことを検出することで実行できますがT(tail)...、それはそのままです。読者のための演習として:)
Pavel Minaev

39

シンプルだと思いmake_arrayます。

template<typename ret, typename... T> std::array<ret, sizeof...(T)> make_array(T&&... refs) {
    // return std::array<ret, sizeof...(T)>{ { std::forward<T>(refs)... } };
    return { std::forward<T>(refs)... };
}

1
取り外しstd::array<ret, sizeof...(T)>return声明。これT&&は、C ++ 14およびC ++ 11で(construct-from-とは対照的に)配列型の移動コンストラクターを無意味に存在させます。
Yakk-Adam Nevraumont 2016

7
私はC ++の人々がそのように単純に呼んでいるのが好きです:-)
Ciro Santilli郝海东冠状病六四事件法轮機能

20

以前の投稿のいくつかのアイデアを組み合わせて、ネストされた構造(GCC4.6でテスト済み)でも機能するソリューションを次に示します。

template <typename T, typename ...Args>
std::array<T, sizeof...(Args) + 1> make_array(T && t, Args &&... args)
{
  static_assert(all_same<T, Args...>::value, "make_array() requires all arguments to be of the same type."); // edited in
  return std::array<T, sizeof...(Args) + 1>{ std::forward<T>(t), std::forward<Args>(args)...};
}

奇妙なことに、戻り値を右辺値参照にすることはできません。ネストされた構造では機能しません。とにかく、ここにテストがあります:

auto q = make_array(make_array(make_array(std::string("Cat1"), std::string("Dog1")), make_array(std::string("Mouse1"), std::string("Rat1"))),
                    make_array(make_array(std::string("Cat2"), std::string("Dog2")), make_array(std::string("Mouse2"), std::string("Rat2"))),
                    make_array(make_array(std::string("Cat3"), std::string("Dog3")), make_array(std::string("Mouse3"), std::string("Rat3"))),
                    make_array(make_array(std::string("Cat4"), std::string("Dog4")), make_array(std::string("Mouse4"), std::string("Rat4")))
                    );

std::cout << q << std::endl;
// produces: [[[Cat1, Dog1], [Mouse1, Rat1]], [[Cat2, Dog2], [Mouse2, Rat2]], [[Cat3, Dog3], [Mouse3, Rat3]], [[Cat4, Dog4], [Mouse4, Rat4]]]

(最後の出力では、pretty-printerを使用しています。)


実際、この構造の型安全性を改善しましょう。すべてのタイプが同じであることは間違いありません。1つの方法は、上記で編集した静的アサーションを追加することです。もう1つの方法はmake_array、次のようにタイプが同じである場合にのみ有効にすることです。

template <typename T, typename ...Args>
typename std::enable_if<all_same<T, Args...>::value, std::array<T, sizeof...(Args) + 1>>::type
make_array(T && t, Args &&... args)
{
  return std::array<T, sizeof...(Args) + 1> { std::forward<T>(t), std::forward<Args>(args)...};
}

どちらの場合も、可変個all_same<Args...>型の特性が必要になります。ここでは一般化から、であるstd::is_same<S, T>(減衰がの混合を可能にするために重要であることに注意してくださいTT&T const &など):

template <typename ...Args> struct all_same { static const bool value = false; };
template <typename S, typename T, typename ...Args> struct all_same<S, T, Args...>
{
  static const bool value = std::is_same<typename std::decay<S>::type, typename std::decay<T>::type>::value && all_same<T, Args...>::value;
};
template <typename S, typename T> struct all_same<S, T>
{
  static const bool value = std::is_same<typename std::decay<S>::type, typename std::decay<T>::type>::value;
};
template <typename T> struct all_same<T> { static const bool value = true; };

注ことmake_array()(十分な最適化フラグ付き!)コピーの一時的によって戻り、コンパイラは右辺値として、あるいはそれ以外の最適化離れ扱いとさせており、std::array集合型であるため、コンパイラは可能な限り最高の施工方法を選択して自由です。

最後に、make_arrayイニシャライザを設定するときにコピー/移動の構築を回避できないことに注意してください。したがってstd::array<Foo,2> x{Foo(1), Foo(2)};、コピー/移動はありませんがauto x = make_array(Foo(1), Foo(2));、引数がに転送されるときに2つのコピー/移動がありますmake_array。可変長初期化子リストを字句的にヘルパーに渡して型とサイズ推定することができないので、あなたはそれを改善できるとは思いません-プリプロセッサーsizeof...に可変個引数の関数がある場合、おそらくそれは可能ですが、できませんコア言語内。


13

末尾の戻り構文の使用はmake_arrayさらに簡略化できます

#include <array>
#include <type_traits>
#include <utility>

template <typename... T>
auto make_array(T&&... t)
  -> std::array<std::common_type_t<T...>, sizeof...(t)>
{
  return {std::forward<T>(t)...};
}

int main()
{
  auto arr = make_array(1, 2, 3, 4, 5);
  return 0;
}

集合クラスの場合は、明示的な型指定が必要です。

/*
struct Foo
{
  int a, b;
}; */

auto arr = make_array(Foo{1, 2}, Foo{3, 4}, Foo{5, 6});

実際、このmake_array実装はsizeof ...演算子にリストされています


c ++ 17バージョン

クラステンプレートの提案に対するテンプレート引数の控除のおかげで、控除ガイドを使用してmake_arrayヘルパーを取り除くことができます

#include <array>

namespace std
{
template <typename... T> array(T... t)
  -> array<std::common_type_t<T...>, sizeof...(t)>;
}

int main()
{
  std::array a{1, 2, 3, 4};
  return 0; 
}

-std=c++1zx86-64 gcc 7.0 でフラグ付きでコンパイル


6
C ++ 17には、すでにこのための控除ガイドが必要です。en.cppreference.com
underscore_d

6

この質問が出されてからかなりの時間が経過していることは承知していますが、既存の回答にはまだいくつかの欠点があると感じているので、少し変更したバージョンを提案したいと思います。以下は、既存の回答のいくつかが欠けていると私が思うポイントです。


1. RVOに依存する必要はありません

一部の回答では、構築されたを返すにはRVOに依存する必要があると述べていarrayます。それは真実ではありません。copy-list-initializationを使用して、一時が作成されないことを保証できます。だから代わりに:

return std::array<Type, …>{values};

私たちはすべきです:

return {{values}};

作る2. 機能をmake_arrayconstexpr

これにより、コンパイル時の定数配列を作成できます。

3.すべての引数が同じ型であることを確認する必要はありません

まず、そうでない場合、リストの初期化では絞り込みが許可されていないため、コンパイラはとにかく警告またはエラーを発行します。第2に、実際に自分でやろうと決心したとしてもstatic_assert(おそらく、より適切なエラーメッセージを提供するため)、生の型ではなく、引数の減衰型を比較する必要があります。例えば、

volatile int a = 0;
const int& b = 1;
int&& c = 2;

auto arr = make_array<int>(a, b, c);  // Will this work?

私たちは単純にしている場合static_assertのことをするabc同じ型を持っている、そしてこのチェックは失敗しますが、それはおそらく、我々が期待するものではありません。代わりに、それらのstd::decay_t<T>タイプ(すべてints)を比較する必要があります。

4.転送された引数を減衰させることにより、配列の値の型を推測します

これはポイント3に似ています。同じコードスニペットを使用しますが、今回は値の型を明示的に指定しないでください。

volatile int a = 0;
const int& b = 1;
int&& c = 2;

auto arr = make_array(a, b, c);  // Will this work?

おそらくを作成したいと思うかもしれませんarray<int, 3>が、既存の回答の実装はおそらくすべてそれを行うことに失敗しています。私たちにできることは、を返す代わりにをstd::array<T, …>返すことstd::array<std::decay_t<T>, …>です。

このアプローチには1つの欠点がありarrayます。cvで修飾された値の型を返すことができなくなります。しかし、ほとんどの場合、のようなものではなくarray<const int, …>const array<int, …>とにかく使用します。トレードオフがありますが、私は合理的なものだと思います。C ++ 17 std::make_optionalもこのアプローチを採用しています。

template< class T > 
constexpr std::optional<std::decay_t<T>> make_optional( T&& value );

上記の点を考慮に入れると、make_arrayC ++ 14での完全に機能する実装は次のようになります。

#include <array>
#include <type_traits>
#include <utility>

template<typename T, typename... Ts>
constexpr std::array<std::decay_t<T>, 1 + sizeof... (Ts)>
make_array(T&& t, Ts&&... ts)
    noexcept(noexcept(std::is_nothrow_constructible<
                std::array<std::decay_t<T>, 1 + sizeof... (Ts)>, T&&, Ts&&...
             >::value))

{
    return {{std::forward<T>(t), std::forward<Ts>(ts)...}};
}

template<typename T>
constexpr std::array<std::decay<T>_t, 0> make_array() noexcept
{
    return {};
}

使用法:

constexpr auto arr = make_array(make_array(1, 2),
                                make_array(3, 4));
static_assert(arr[1][1] == 4, "!");

6

C ++ 11は、(ほとんど?)stdコンテナのこの初期化方法をサポートします


1
しかし、OPは配列のサイズを指定したくないと思いますが、サイズはstd :: arrayのテンプレートパラメーターです。したがって、std :: array <unsigned int、5> n = {1,2,3,4,5}のようなものが必要です。
juanchopanza

std::vector<>明示的な整数は必要ありませんstd::array。なぜそうするのかわかりません。
リチャード

@Richard、なぜならstd :: vectorは動的なサイズを持ち、std :: arrayは固定サイズだからです。これを参照してください:en.wikipedia.org/wiki/Array_
C%2B%2B

@juanchopanzaですが、{...}構文はコンパイル時の一定の範囲を意味するため、ctor は範囲を推定できるはずです。
リチャード

1
std::initializer_list::sizeconstexpr関数ではないため、このように使用することはできません。ただし、libstdc ++(GCCに同梱されている実装)から、そのバージョンを使用する計画がありますconstexpr
Luc Danton、2011年

5

(@dypによるソリューション)

注:C ++ 14std::index_sequence)が必要です。std::index_sequenceC ++ 11で実装することもできますが。

#include <iostream>

// ---

#include <array>
#include <utility>

template <typename T>
using c_array = T[];

template<typename T, size_t N, size_t... Indices>
constexpr auto make_array(T (&&src)[N], std::index_sequence<Indices...>) {
    return std::array<T, N>{{ std::move(src[Indices])... }};
}

template<typename T, size_t N>
constexpr auto make_array(T (&&src)[N]) {
    return make_array(std::move(src), std::make_index_sequence<N>{});
}

// ---

struct Point { int x, y; };

std::ostream& operator<< (std::ostream& os, const Point& p) {
    return os << "(" << p.x << "," << p.y << ")";
}

int main() {
    auto xs = make_array(c_array<Point>{{1,2}, {3,4}, {5,6}, {7,8}});

    for (auto&& x : xs) {
        std::cout << x << std::endl;
    }

    return 0;
}

std :: array要素のデフォルトの初期化を見落としました。現在修正を探しています。
Gabriel Garcia 14

@dyp私はあなたのコードで答えを更新しました。あなたがあなた自身の答えを書くことに決めた場合、私に知らせてください。ありがとうございました。
ガブリエルガルシア

1
大丈夫です。一時配列をバインドして長さを推定するのはあなたの考えです。コードがコンパイルされるかどうかは確認しませんでした。私はそれがまだあなたの解決策であると思います、そしていくつかの改良を加えて答えます;)しかしmake_array、パピーの答えのように可変長には利点がないと主張するかもしれません。
dyp '30 / 12/14

正しい。さらに、テンプレートはイニシャライザリストから型を推定できません。これは、質問の要件の1つです(ネストされたブレース初期化)。
ガブリエルガルシア

1

С++ 17コンパクトな実装。

template <typename... T>
constexpr auto array_of(T&&... t) {
    return std::array{ static_cast<std::common_type_t<T...>>(t)... };
}

0

std :: arrayが制約ではなく、Boostがある場合は、を見てくださいlist_of()。これは、Cタイプの配列の初期化とは異なります。しかし、閉じます。


それは良いものです。ネストされた構造の割り当てにそれを使用することに関する同様の質問は、こちら
ください。Using

0

配列メーカータイプを作成します。

オーバーロードoperator,して、参照を介して各要素を前の要素にチェーンする式テンプレートを生成します。

finish配列メーカーを取得し、参照のチェーンから直接配列を生成する無料の関数を追加します。

構文は次のようになります。

auto arr = finish( make_array<T>->* 1,2,3,4,5 );

それ{}だけであるように、それはベースの構築を許可しませんoperator=。使用したい場合は=、動作させることができます。

auto arr = finish( make_array<T>= {1}={2}={3}={4}={5} );

または

auto arr = finish( make_array<T>[{1}][{2}[]{3}][{4}][{5}] );

これらはどれも優れたソリューションのようには見えません。

variardicsを使用すると、コンパイラーによって課されるvarargsの数の制限に制限され{}、部分構造の再帰的な使用がブロックされます。

結局、本当に良い解決策はありません。

私がやっていることは、コードを記述して、コードT[]std::arrayデータの両方を無意識に消費することです。どちらにフィードしてもかまいません。時々これは私の転送コードが[]配列をstd::array透過的に注意深くs に変換しなければならないことを意味します。


1
「これらは良い解決策のようには見えません。」P:私はあまりにも、言うことです
キャップ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.