4

ポイントとのガウス積分を実行するコードを書いています。ここで、コンパイル時定数です。nn

与えられた について、n横座標と重みを計算する方法を知っています。計算は、それぞれの異なる に対してゼロから行う必要がありますn

今、私はこれらの行に沿って何かをします:

// Several structs like this one (laguerre, chebyshev, etc).
template <size_t n>
struct legendre
{
    static const size_t size = n;
    static const double x[n];
    static const double w[n];
};

template <typename Rule, typename F>
double gauss_quadrature (F&& f)
{
    double acc = 0;
    for (size_t j = 0; j < Rule::size; j++)
        acc += Rule::w[j] * f (Rule::x[j]);

    return acc;
}

次のように使用します。

double i = gauss_quadrature<legendre<12>> (f);

legendre<12>これで、次のようにして、 の係数を変換単位に特化できます。

template <>
const legendre<12>::x[12] = { ... };

template <>
const legendre<12>::w[12] = { ... };

12 ポイントの Gauss-Legendre のみを使用する限り、すべて問題ありません。

現在、さまざまな数のポイントを試しており、重みとノードを生成する方法を知っています。たとえば、ルーチンを提供できます

void compute_legendre_coeffs (size_t n, double* w, double* x);

と :

  • を呼び出すgauss_quadrature<legendre<n>>と、テンプレートlegendre<n>が自動的にインスタンス化されます (これが該当します)。
  • legendre<n>がいくつかのコンパイル時にインスタンス化されるとき、上記を main の前のある時点で呼び出して、メンバー配列とメンバー配列を埋めるnようにしたいと思います。compute_legendre_coeffsxwどうすればこれを達成できますか?

最初に配列を定義する必要があることはわかっています。

template <size_t n>
const double legendre<n>::x[n] = {};

template <size_t n>
const double legendre<n>::w[n] = {};

しかし、それらを初期化する方法が思いつきません。誰もそうするためのトリックを持っていますか?

4

4 に答える 4

2

配列を次のように変換しますstd::array

#include <array>
template<int n> struct legendre {
    static const std::array<double, n> x;
};
void compute_xs(int n, double *xs) {
    ...
}
template<int n> std::array<double, n> make_xs() {
    std::array<double, n> xs;
    compute_xs(n, xs.data());
    return xs;
}
template<int n> const std::array<double, n> legendre<n>::x = make_xs<n>();

xこれは、と係数を別々に計算することを意味しwますが、これが効率的でない場合の回避策があります。たとえば、次のようになります。

template<int n> struct legendre_coeffs {
    std::array<double, n> x, w;
    legendre_coeffs(): x(), w() { compute_legendre_coeffs(n, w.data(), x.data()); }
};
template<int n> struct legendre {
    static const legendre_coeffs coeffs;
    static const double (&x)[n], (&w)[n];
};
template<int n> const legendre_coeffs legendre<n>::coeffs;
template<int n> const double (&legendre<n>::x)[n]
    = *reinterpret_cast<const double (*)[n]>(legendre<n>::coeffs::x.data());
template<int n> const double (&legendre<n>::w)[n]
    = *reinterpret_cast<const double (*)[n]>(legendre<n>::coeffs::w.data());
于 2012-12-04T15:17:31.383 に答える
1
template <size_t n>
class legendre
{
public:
    static const size_t size = n;
    static const double (&getX())[n] {
       init();
       return x;
    }
    static const double (&getW())[n] {
       init();
       return x;
    }
private:
    static double x[n];
    static double w[n];
    static void init() {
       static bool _ = do_init(x,y);
    }
    static bool do_init( double *x, double *y ) {
       // do the computation here, use local vars x, y
       return true;
    }
};
template <size_t n>
double legendre<n>::x[n];
template <size_t n>
double legendre<n>::w[n];

アクセサを提供することにより、クラスへのエントリポイントを制御できます。アクセサーinitは、ローカル静的変数の初期化を使用do_initして、プログラムの存続期間中に1回だけ呼び出す関数にディスパッチします。do_initメンバーの実際の初期化を行います。

ノート:

コンパイラーによっては、これはスレッドセーフではない場合があります(つまり、すべてのC ++ 03コンパイラーが静的変数のスレッドセーフな初期化を提供するわけではありません。つまりdo_init、アルゴリズムによっては、が複数回並行して呼び出される可能性があります。問題ではありません。つまりdo_init、値を脇に計算して書き込むだけの場合、最終的な結果は同じになるため、潜在的な競合状態は関係ありません)。一部のコンパイラは、1回限りの実行を保証するメカニズムを提供します(ブーストにはそのようなメカニズムがあると思います)。または、ドメインによっては、スレッドを開始する前に係数をプライミングできる場合があります。

constこの場合、実際のアレイを作成する必要があるため、実際のアレイを作成することはできません。これは、可能なマイクロ最適化以外の問題ではないはずです(つまり、コンパイラーは係数の値を認識していないため、コンパイル時に部分式の評価を実行できません)。

于 2012-12-04T16:07:32.467 に答える
1

まず第一に、C++03 を使用してコンパイル時に完全に初期化を行うことはできません (そう、設計上!) - それを行う唯一の方法は C++ テンプレートを使用することですが、double を渡すことはできません。テンプレートパラメータとして。

C++11 を使用すると状況が改善されます -- 使用することはできますが、それが十分に自明constexprである場合に限ります。compute_legendre_coeffs()

または、クラス宣言の事実によっていくつかのアクションを実行する必要があるときに使用するトリックが1つあります。たとえば、どこかにsmthを登録して、このようなライブラリまたはsmthを介してシリアライゼーション機能を提供します。

その配列を初期化するために静的コンストラクターのイディオムを使用できます...同様の理由で、次のコードを使用します。

template <
    typename Derived
  , typename Target = Derived
>
class static_xtors
{
    // This class will actually call your static init methods...
    struct helper
    {
        helper()
        {
        Target::static_ctor();
        }

        ~helper()
        {
        Target::static_dtor();
        }
    };
    // ... because your derived class would inherit this member from static_xtor base
    static helper s_helper;

    // The rest is needed to force compiler to instantiate everything required stuff
    // w/o eliminate as unused...
    template <void(*)()>
    struct helper2 {};

    static void use_helper()
    {
    (void)s_helper;
    }
    helper2<&static_xtors::use_helper> s_helper2;

    virtual void use_helper2()
    {
    (void)s_helper2;
    }

public:
    /// this is not required for your case... only if later you'll have
    /// a hierarchy w/ virtuals
    virtual ~static_xtors() {}
};

template <
    typename Derived
, typename Target
>
typename static_xtors<Derived, Target>::helper
static_xtors<Derived, Target>::s_helper;

次に、static_xtors クラスを継承し、2 つの静的メソッドを実装する必要があります。void static_ctor()-- これにより、配列が初期化され、(あなたの場合は) 空になりますvoid static_dtor()... つまり、次のようになります。

template <size_t n>
struct legendre : public static_xtors<legendre<n>>
{
    static const size_t size = n;
    static double x[n];
    static double w[n];

    static void static_ctor()
    {
        compute_legendre_coeffs(n, x, w);
    }
    static void static_dtor()
    {
        // nothing to do
    }
};

template <size_t n>
static double legendre<n>::x[n];

template <size_t n>
static double legendre<n>::w[n];

お気づきかもしれませんが、もはや const ではありません :( -- それらを再度非表示xにして、呼び出し元が使用する静的ゲッターを追加wしようとする場合があります...また、内部配列は実行時に初期化されますが、関数のに(そして一度だけ)...constprivatemain


または ... で再生しますが、初期化リストを使用すると次のようになるため、constexpr初期化関数を再設計する必要があるようです (何らかの方法で)。

template <size_t n>
static double legendre<n>::x[n] = { calc_coeff_x<0>(), calc_coeff_x<1>(), calc_coeff_x<2>(), ... }

...そして間違いなく、特殊化(および広範なマクロの使用)なしでは実行できません。しかし、おそらく可変個のテンプレートが役立つかもしれません...あなたの関数と考える時間についての詳細を知る必要があります:))

于 2012-12-04T15:44:04.027 に答える
0

関数をイニシャライザ クラス テンプレートに変換してみることもできます。そのパラメータは、初期化するクラス/構造体になります。次に、そのクラス テンプレートを変更して、初期化子の定数インスタンスを含めます。最後に、初期化クラスのコンストラクターで、実際の初期化を行うコードをトリガーします。

初期化が複数回発生しないように、初期化クラスへのアクセス [1] も保護する必要があります。

ご覧のとおり、クラス インスタンスがコンストラクタ コードを呼び出し、テンプレート インスタンスが定数データを初期化するという事実を利用するという考え方です。

以下は、テンプレートなしで可能な (そして単純な) 実装です。

struct legendre_init {
    legendre_init(){
        compute_legendre_coeffs (T::n, T::w, T::x);
    }
 };


template <size_t n>
struct legendre
{
    typedef legendre<n> self_type;
    static const size_t size = n;
    static const double x[n];
    static const double w[n];
    static const legendre_init _l;
};

これは別の見方で、今回は初期化を構造体に直接入れています。

template <class T>
class T_init {
    public:
    T_init(){
        T::_init();
    }
 };


template <size_t n>
struct legendre
{
    typedef legendre<n> self_type;
    static const size_t size = n;
    static const double x[n];
    static const double w[n];
    static const T_init<self_type> _f;
    static void _init(){
        compute_legendre_coeffs (self_type::n, self_type::w, self_type::x);
    }
};

このソリューションの興味深い特徴は、定数インスタンスが構造T_init体内でスペースを取らないことです。T初期化ロジックはそれを必要とするクラスにバンドルされており、T_initテンプレートはそれを自動的に有効にするだけです。

[1] Xeo は std::call_once テンプレートについて言及しました。

于 2012-12-04T15:17:07.577 に答える