68

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

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

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

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

例えば:

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は不可)

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

4

14 に答える 14

87

最も近い方法は、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";
}
于 2010-06-05T18:54:12.703 に答える
8

C ++ 17以降、constexprラムダを使用してその場で呼び出すことができます。唯一の「欠点」はstd::array、cスタイルの配列の代わりに使用する必要があることです。

constexpr auto myArray{[]() constexpr{
    std::array<MyType, MySize> result{};
    for (int i = 0; i < MySize; ++i)
    {
       result[i] = ...
    }
    return result;
}()};

例として、2の累乗で配列を作成する方法を説明します。

constexpr auto myArray{[]() constexpr{
    constexpr size_t size = 64;
    std::array<long long, size> result{};
    result[0] = 1;
    for (int i = 1; i < size; ++i)
    {
       result[i] = result[i - 1] * 2;
    }
    return result;
}()};

ご覧のとおり、配列の前のセルを参照することもできます。

この手法は、IILEまたは即時呼び出しラムダ式と呼ばれます。

于 2020-07-26T09:58:19.320 に答える
6

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

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

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

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

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

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

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

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

于 2010-06-05T10:12:28.503 に答える
4

テンプレートを使用してネストされた構造体を構築し、それを適切なタイプの配列としてキャストするのはどうですか。以下の例は私には有効ですが、未定義の動作に足を踏み入れているか、非常に近く歩いているような気がします。

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

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

于 2010-06-05T04:11:57.330 に答える
2

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

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

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

于 2010-06-04T23:11:10.467 に答える
2

Boost.Assignmentのようなものは、標準のコンテナーで機能する可能性があります。本当に配列を使用する必要がある場合は、Boost.Arrayに沿って使用できます。

于 2010-06-04T23:21:57.063 に答える
2

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

uint32_t tab[sizeof(A)]= {A::value...};
于 2011-08-20T21:03:16.747 に答える
1

1つの質問。あなたはそのようにそれをすることができます。

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;
}
于 2010-06-04T23:40:21.453 に答える
1

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

真剣に、コードジェネレーターはあなたの人生をはるかに楽にするでしょう。

于 2010-06-05T19:08:08.153 に答える
0

メタプログラミングでできることはたくさんあります。しかし、最初に質問したいのですが、なぜあなたはあなたの場合にこれをしたいのですか?そのような配列を別の場所で宣言する必要があるかどうかは理解できたので、同じものを何度も書き直す必要がありました。これはあなたの場合ですか?

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

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

また、配列エントリのほとんどが同じである状況を処理することもできますが、いくつかの変な創造性があります:)

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

于 2010-06-04T23:15:00.960 に答える
0

ブーストから、

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

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

于 2010-06-04T23:29:21.387 に答える
0

テンプレートを再帰的に使用する

template<uint64_t N>
constexpr uint64_t Value()
{
    return N + 100;
}

// recursive case
template<uint64_t N, uint64_t... args>
struct Array : Array<N - 1, Value<N - 1>(), args...> {
};

// base case
template<uint64_t... args>
struct Array<0, Value<0>(), args...> {
    static std::array<uint64_t, sizeof...(args) + 1> data;
};

template<uint64_t... args>
std::array<uint64_t, sizeof...(args) + 1> Array<0, Value<0>(), args...>::data = {Value<0>(), args...};

int main()
{
    Array<10> myArray;
    for (size_t i = 0; i < myArray.data.size(); ++i) {
        cout << myArray.data[i] << endl;
    }

    return 0;
}
于 2020-07-08T03:55:02.537 に答える
0

array <int、SIZE> t

前述のように、C++17ではconstexprを使用できます

vector<int> countBits(int num) {
    static constexpr int SIZE = 100000;
    static constexpr array<int, SIZE> t {[]() constexpr {
            constexpr uint32_t size = SIZE;
            array<int, size> v{};
            for (int i = 0; i < size; i++)
                v[i] =  v[i>>1] + (i & 1); // or simply v[i] = __builtin_popcount(i);
            return v;}()};

    vector<int> v(t.begin(), t.begin() + num + 1);
    return v;
}

ただし、c++配列型を使用する必要があります。


intt[サイズ]

本当にC配列を使用したい場合は、次のトリックint [SIZE]を使用するのとは異なります。array<int, SIZE>

グローバル配列を宣言してから、メイン内の値を計算して、コンパイル時に静的配列を作成します。

int w[100000] = {0};

vector<int> countBits(int num) {
    vector<int> v(w, w + num + 1);
    return v;
}

int main(void) {
    for (int i = 0; i < 100000; i++)
        w[i] = __builtin_popcount(i);
}


結果

実行時の出力(確かにひどい):

OK  ( 591 cycles)        0,1,1, -> 0,1,1,
OK  ( 453 cycles)        0,1,1,2,1,2, -> 0,1,1,2,1,2,
OK  ( 455 cycles)        0,1,1,2,1,2,2,3,1,2,... -> 0,1,1,2,1,2,2,3,1,2,...

constexpr配列を使用した平均出力:

OK  (   1 cycles)        0,1,1, -> 0,1,1,
OK  (   2 cycles)        0,1,1,2,1,2, -> 0,1,1,2,1,2,
OK  (  24 cycles)        0,1,1,2,1,2,2,3,1,2,... -> 0,1,1,2,1,2,2,3,1,2,...

2番目の方法での平均出力(C ++配列のオーバーヘッドを取り除くので少し速くなります):

OK  (   0 cycles)        0,1,1, -> 0,1,1,
OK  (   1 cycles)        0,1,1,2,1,2, -> 0,1,1,2,1,2,
OK  (  23 cycles)        0,1,1,2,1,2,2,3,1,2,... -> 0,1,1,2,1,2,2,3,1,2,...

基準

私はベンチマークしました:

#include <vector>
#include <string>
#include <cstdint>
#include <array>
#include <iostream>
#include <ctime>
#include <iterator>
#include <sstream>

using namespace std;

vector<int> nums = {2, 5};
vector<vector<int>> expected = {{0,1,1}, {0,1,1,2,1,2}}; // feel free to add more tests

for (int i = 0; i < expected.size(); i++) {
        clock_t start = clock();
        vector<int> res = countBits(nums[i]);
        double elapsedTime = (clock() - start);
        printf("%s  \033[30m(%4.0lf cycles)\033[0m\t %s -> %s\n", (expected[i] == res) ? "\033[34mOK" : "\033[31mKO", elapsedTime, toString(res).c_str(), toString(expected[i]).c_str());
}
于 2021-04-19T14:43:25.717 に答える
0

時間の経過とともに、constexprC ++では関数、メソッド、ラムダの機能が大幅に向上しました。C ++ 17では、forループとif条件を使用してconstexpr、コンパイル時に配列の内容を実際に計算できます。素数ふるいについては、次の例を参照してください。

#include <array>
#include <cmath>

template<unsigned N>
constexpr auto primesieve() {
    std::array<bool, N+1> primes {};
    // From C++20, the init loop may be written as:   primes.fill(true);
    for(unsigned n = 0; n <= N; n++) {
        primes[n] = true;
    }
    unsigned maxs = sqrt(N);
    for(unsigned n = 2; n <= maxs; n++) {
        if(primes[n]) {
            for(unsigned j = n + n; j <= N; j += n) {
                primes[j] = false;
            }
        }
    }
    return primes;
};

extern constexpr std::array<bool, 20> myprimes { primesieve<19>() };

このコードのアセンブリ出力を見ると、myprimes配列のデータバイトのみが表示され、単一のプロセッサ命令は表示されません。最適化がオフになっている場合でも、すべての計算はコンパイル時に実行されます。

ただし、他の人がすでに書いているように、コンパイラでのC ++コードの解釈は、コンパイルされたC++コードの実行よりもはるかに遅くなります。したがって、コンパイル時に合理的に実行できるこれらの初期化には、実行時に最大で数ミリ秒かかります。

しかし、const/constexpr初期化には多くの利点があります。つまり、同じアプリケーションを実行している異なるプロセス間で共有される一定のメモリに移動します。一方、実行時の動的初期化は、各プロセスのプライベートメモリに送られます。

そして、機能はさらに向上しています。C ++ 20は、関数内std::stringおよび関数内のサポートも追加します。ただし、関数から空でない文字列とベクトルを返すことはできません。これまで、この機能を実装しているのはMicrosoftコンパイラのみです。std::vectorconstexprconstexpr

于 2021-04-30T08:57:37.373 に答える