web-dev-qa-db-ja.com

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

(注:この質問は、要素の数を指定しなくても、ネストされた型を直接初期化できるようにすることに関するものです。)
この質問 は、int arr[20];のようなC配列の残りの使用法について説明しています。 彼の答え で、@ James KanzeはC配列の最後の要塞の1つを示し、それは一意の初期化特性です。

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

要素の数を指定する必要はありません。次に、C++ 11関数std::beginおよびstd::endからの<iterator>または独自のバリアント )で繰り返し処理し、そのことを考える必要さえありません。サイズ。

さて、std::arrayで同じことを実現する(おそらくTMP)方法はありますか?マクロを使用すると、見栄えが良くなります。 :)

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

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

#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

そして、例を見つけることができます here

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

struct A{ int a; int b; };

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

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

132
Xeo

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

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++コンパイラは、直接初期化と同じくらい高速になるように最適化できると期待しています。

60
Pavel Minaev

単純な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)... };
}
37
Puppy

以前の投稿からのいくつかのアイデアを組み合わせて、ネストされた構造(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>_から一般化しています(T、_T&_、_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()は、copy-of-temporaryによって返されることに注意してください。コンパイラは(十分な最適化フラグを使用して)右辺値として扱うか、そうでなければ最適化を許可します。_std::array_は集約型そのため、コンパイラは可能な限り最良の構築方法を自由に選択できます。

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

20
Kerrek SB

末尾のリターン構文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 ... operator にリストされています


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; 
}

X86-64 gcc 7.0で-std=c++1zフラグを使用してコンパイル

11
wiped

この質問が出されてからかなり時間が経ったことはわかっていますが、既存の回答にはまだいくつかの欠点があると感じているので、少し修正したバージョンを提案したいと思います。以下は、いくつかの既存の答えが欠けていると思う点です。


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

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

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

私たちはすべきです:

return {{values}};

2. make_arrayconstexpr関数にする

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

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

まず、そうでない場合、コンパイラはリスト初期化ではナローイングが許可されないため、警告またはエラーを発行します。第二に、(おそらくより良いエラーメッセージを提供するために)独自のstatic_assert事を行うことに決めたとしても、おそらく引数を比較する必要があります 'decayed生の型ではなく型。例えば、

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

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

単純にstatic_assertingでab、およびcが同じ型である場合、このチェックは失敗しますが、おそらくそれは私たちがすることではありません期待します。代わりに、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つの欠点があります。それ以上cvで修飾された値型のarrayを返すことはできません。しかし、ほとんどの場合、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 );

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

#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
Zizheng Tai

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

6
Richard

(@dypによる解決策)

注:C++ 14std::index_sequence)。 std::index_sequence C++ 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;
}
5
Gabriel Garcia

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

0
yasouser

アレイメーカータイプを作成します。

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

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

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

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を使用すると、可変引数の数にコンパイラーが課す制限に制限され、サブ構造に対する{}の再帰的な使用がブロックされます。

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

私がやることは、T[]std::arrayの両方のデータを消費するようにコードを書くことです不可知論的に私はそれを養います。時には、これは私の転送コードが透過的に[]配列をstd::arraysに注意深く変換する必要があることを意味します。