web-dev-qa-db-ja.com

C ++でコンパイル時にプログラムで静的配列を作成する

コンパイル時に静的配列を次のように定義できます。

const std::size_t size = 5;    
unsigned int list[size] = { 1, 2, 3, 4, 5 };

質問1-さまざまな種類のメタプログラミング手法を使用して、コンパイル時にこれらの値を「プログラム的に」割り当てることができますか?

質問2-配列内のすべての値が同じbarrであると仮定すると、プログラムの方法でコンパイル時に値を選択的に割り当てることは可能ですか?

例えば:

const std::size_t size = 7;        
unsigned int list[size] = { 0, 0, 2, 3, 0, 0, 0 };
  1. C++ 0xを使用するソリューションは大歓迎です
  2. 配列は非常に大きく、数百要素の長さになる場合があります
  3. 今のところ、配列はPODタイプのみで構成されます。
  4. また、静的コンパイル時準拠の方法で、配列のサイズが事前にわかっていると想定することもできます。
  5. ソリューションはC++である必要があります)(スクリプトなし、マクロなし、ppなし、コードジェネレーターベースのソリューションpls)

PDATE: Georg Fritzscheのソリューションは驚くべきものであり、msvcおよびintelコンパイラでコンパイルするには少し作業が必要ですが、それでもこの問題に対する非常に興味深いアプローチです。

63
Hippicoder

最も近い方法は、C++ 0x機能を使用して、可変引数テンプレート引数リストからテンプレートのローカルまたはメンバー配列を初期化することです。
これはもちろん、最大のテンプレートのインスタンス化の深さによって制限されており、実際にあなたのケースで顕著な違いが生じるかどうかを測定する必要があります。

例:

template<unsigned... args> struct ArrayHolder {
    static const unsigned data[sizeof...(args)];
};

template<unsigned... args> 
const unsigned ArrayHolder<args...>::data[sizeof...(args)] = { args... };

template<size_t N, template<size_t> class F, unsigned... args> 
struct generate_array_impl {
    typedef typename generate_array_impl<N-1, F, F<N>::value, args...>::result result;
};

template<template<size_t> class F, unsigned... args> 
struct generate_array_impl<0, F, args...> {
    typedef ArrayHolder<F<0>::value, args...> result;
};

template<size_t N, template<size_t> class F> 
struct generate_array {
    typedef typename generate_array_impl<N-1, F>::result result;
};

1..5ケースの使用法:

template<size_t index> struct MetaFunc { 
    enum { value = index + 1 }; 
};

void test() {
    const size_t count = 5;
    typedef generate_array<count, MetaFunc>::result A;

    for (size_t i=0; i<count; ++i) 
        std::cout << A::data[i] << "\n";
}
77
Georg Fritzsche

まあ、あなたの要件はとても曖昧なので、それらについて何もするのは難しいです...主な問題はもちろんです:それらの価値はどこから来るのですか?

とにかく、C++でのビルドは4つのステップと考えることができます。

  • ビルド前の手順:他の形式からのヘッダー/ソースのスクリプト生成
  • 前処理
  • テンプレートのインスタンス化
  • 適切なコンパイル

スクリプトの生成を除外したい場合は、前処理とメタテンプレートプログラミングの2つの選択肢があります。

私が知っている限り、コンパイル時に2つの配列を連結することは不可能なので、メタテンプレートプログラミングがここでトリックを行う方法を知る方法はありません。したがって、今日の救世主が残っています:プリプロセッサプログラミング

Boost.Preprocessor を支援するために、本格的なライブラリを使用することをお勧めします。

ここで特に興味深いのは:

ここで、どこから値を選択するかがわかっていれば、より意味のある例を挙げることができます。

7
Matthieu M.

テンプレートを使用してネストされた構造体を構築し、それを適切な型の配列としてキャストしてください。以下の例は私には有効ですが、踏み込んでいるか、未定義の動作に非常に近いところに歩いているかのどちらかです。

#include <iostream>

template<int N>
struct NestedStruct
{
  NestedStruct<N-1> contained;
  int i;
  NestedStruct<N>() : i(N) {}
};

template<>
struct NestedStruct<0> 
{
  int i;
  NestedStruct<0>() : i(0) {}
};

int main()
{
  NestedStruct<10> f;
  int *array = reinterpret_cast<int*>(&f);
  for(unsigned int i=0;i<10;++i)
  {
    std::cout<<array[i]<<std::endl;
  }
}

そしてもちろん、配列はコンパイル時に初期化されない(これは不可能だと思います)が、配列に入る値はコンパイル時に計算され、通常の配列と同じようにアクセスできると主張できます。 。私はあなたが得ることができる限り近いと思います。

5

コンパイラー時に本当にそれを行う必要がありますか?静的初期化時に行う方がはるかに簡単です。このようなことができます。

#include <cstddef>
#include <algorithm>

template<std::size_t n>
struct Sequence
{
    int list[n];

    Sequence()
    {
        for (std::size_t m = 0; m != n; ++m)
        {
            list[m] = m + 1;
        }
    }
};

const Sequence<5> seq1;

struct MostlyZero
{
    int list[5];

    MostlyZero()
    {
        std::fill_n(list, 5, 0); // Not actually necessary if our only
                                 // are static as static objects are
                                 // always zero-initialized before any
                                 // other initialization
        list[2] = 2;
        list[3] = 3;
    }
};

const MostlyZero mz1;

#include <iostream>
#include <ostream>

int main()
{
    for (std::size_t n = 0; n != 5; ++n)
    {
        std::cout << seq1.list[n] << ", " << mz1.list[n] << '\n';
    }
}

必要に応じて、構造体の外側にリストをプッシュすることもできますが、このようにすっきりしていると思いました。

2
CB Bailey

Boost.Assignment のようなものは、標準のコンテナで機能します。本当に配列を使用する必要がある場合は、 Boost.Array と共に使用できます。

2
danielkza

いつか(常にではない)そのような配列は、型の配列から生成されます。たとえば、既にテンプレートなどの可変クラスリストがあり、カプセル化されたuint32_t値を保存する場合は、次を使用できます。

uint32_t tab[sizeof(A)]= {A::value...};
2
kwesolowski

質問はありません。そのようにすることができます。

template <int num, int cur>
struct ConsequentListInternal {
    enum {value = cur};
    ConsequentListInternal<num-1,cur+1> next_elem;
};

template <int cur>
struct ConsequentListInternal<0, cur> {
    enum {value = cur};
};

template <int v>
struct ConsequentList {
    ConsequentListInternal<v, 0> list;
};

int main() {
    ConsequentList<15> list;
    return 0;
}
1
Max

コードジェネレーターを使用するだけです。テーブルまたは数学関数を使用して、必要なコードを生成できる1つ以上のテンプレートを作成します。次に、生成したファイルをアプリに含めます。

真剣に、コードジェネレーターはあなたの人生をずっと楽にしてくれるでしょう。

0
Rui Curado

メタプログラミングでできることはたくさんあります。しかし、最初に私が尋ねたいのは:なぜあなたはあなたのケースでこれをしたいのですか?そのような配列を異なる場所で宣言する必要があるかどうかを理解できたので、同じものを複数回書き換える必要がありました。これはあなたのケースですか?

「プログラムで定義する」と言うことで、次のことを提案します。

#define MyArr(macro, sep) \
    macro(0) sep \
    macro(0) sep \
    macro(2) sep \
    macro(3) sep \
    macro(0) sep \
    macro(0) sep \
    macro(0)

これまでに、必要なすべての値を最も抽象的な方法で定義しました。ところで、これらの値が実際にあなたにとって意味がある場合-宣言に追加することができます:

#define MyArr(macro, sep) \
    macro(0, Something1) sep \
    macro(0, Something2) sep \
    // ...

さて、上記の宣言に命を吹き込みましょう。

#define NOP
#define COMMA ,
#define Macro_Count(num, descr) 1
#define Macro_Value(num, descr) num

const std::size_t size = MyArr(Macro_Count, +); 
unsigned int list[size] = { MyArr(Macro_Value, COMMA) };

また、ほとんどの配列エントリが同じであるという状況を処理できます。

しかし、常に自問する必要があります。これは本当に価値がありますか?ご覧のとおり、コードをパズルに変えるからです。

0
valdo

ブーストから、

boost::mpl::range_c<int,1,5>

コンパイル時に1〜5のソートされた数値のリストを生成します。 2番目については、値を変更する基準はありません。リストが作成されたら、新しい変数を再定義して再定義することはできないと確信しています。

0
Michael Dorgan