C++ 模板类的静态成员数组的延迟初始化

C++ 模板类的静态成员数组的延迟初始化,c++,templates,static-members,C++,Templates,Static Members,我正在编写代码来执行n点,其中n是编译时常量 对于给定的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

我正在编写代码来执行
n
点,其中
n
是编译时常量

对于给定的
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;
}
以及:

  • 当我调用
    gauss_quarture
    时,模板
    legendre
    会自动实例化(就是这种情况)
  • legendre
    被实例化一段编译时间
    n
    时,我希望在main之前的某个点调用上面的
    compute\u legendre\u coefs
    ,以便它填充
    x
    w
    成员数组。
    我如何实现这一点
我知道必须首先定义数组:

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

template <size_t n>
const double legendre<n>::w[n] = {};
模板
常数双勒让德::x[n]={};
模板
常量双勒让德::w[n]={};

但是我想不出一个方法来初始化它们。有人有这样做的诀窍吗?

也许您可以尝试将函数转换为初始值设定项类模板,其参数将是您要初始化的类/结构。然后更改该类模板以包含初始值设定项的常量实例。最后,让initializer类的构造函数触发执行实际初始化的代码

您可能还应该保护对initializer类的访问[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;
};
struct legendre\u init{
legendre_init(){
计算勒让德系数(T::n,T::w,T::x);
}
};
模板
勒让德结构
{
typedef legendre self_类型;
静态常数size\u t size=n;
静态常数双x[n];
静态常数双w[n];
静态常数legendre_init;
};
下面是另一个例子,这次将初始化直接放在结构中:

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_init(){
T::_init();
}
};
模板
勒让德结构
{
typedef legendre self_类型;
静态常数size\u t size=n;
静态常数双x[n];
静态常数双w[n];
静态常数T_init f;
静态void _init(){
计算勒让德系数(自我类型::n,自我类型::w,自我类型::x);
}
};
此解决方案的有趣特性是
T_init
常量实例不应占用
T
结构中的任何空间。初始化逻辑与需要它的类捆绑在一起,而
T_init
模板仅自动启用它


[1] Xeo提到了std::call_once模板,它在这里很方便。

将数组转换为
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>();

首先,在编译时,你不能用C++ 03完全完成初始化(YEP,按设计!)——唯一的方法是使用C++模板,但是你不能传递一个双模板参数。 通过C++11,情况变得更好了——您可以使用
constepr
,但前提是您的
compute\u legendre\u coefs()
足够简单

或者,当需要根据类声明的事实执行某些操作时,我会使用一个技巧——例如,在某处注册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;
正如您可能注意到的,
x
w
不再是常量:(-您可以尝试使它们
const
再次隐藏到
private
并添加静态getter供调用方使用……此外,您的内部数组将在运行时初始化,但
main
函数之前(仅一次)


或者播放w/
constexpr
…但似乎需要重新设计初始化器函数(不知何故),因为使用初始化列表,它应该如下所示:

double i = gauss_quadrature<legendre<12>> (f);
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];
template <size_t n>
static double legendre<n>::x[n] = { calc_coeff_x<0>(), calc_coeff_x<1>(), calc_coeff_x<2>(), ... }
模板
静态双勒让德::x[n]={calc_coeff_x(),calc_coeff_x(),calc_coeff_x(),…}
…而且在没有专门化(以及大量使用宏)的情况下,您肯定无法做到这一点。 但可变模板可能会有所帮助……需要了解更多有关函数的详细信息,并需要时间思考:)

模板
勒让德阶级
{
公众:
静态常数size\u t size=n;
静态常量双精度(&getX())[n]{
init();
返回x;
}
静态常量双精度(&getW())[n]{
init();
返回x;
}
私人:
静态双x[n];
静态双w[n];
静态void init(){
静态bool=do_init(x,y);
}
静态bool do_init(双*x,双*y){
//在这里进行计算,使用局部变量x,y
返回true;
}
};
模板
双勒让德::x[n];
模板
双勒让德::w[n];
通过提供访问器,您可以控制类的入口点。访问器分派给
init
函数,该函数使用局部静态变量的初始化在程序生命周期内仅调用一次
do_init
do_init
执行成员的实际初始化

注:

根据编译器的不同,这可能不是线程安全的(也就是说,并非所有C++03编译器都提供静态变量的线程安全初始化,这反过来意味着
do_init
可能会被并行调用多次,这取决于可能存在或不存在问题的算法——也就是说,如果
do_init
将值计算到一边并只写入它们,则潜在的竞争条件是i一些编译器提供了保证一次性执行的机制(我相信boost已经成功了)
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;
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];
template <size_t n>
static double legendre<n>::x[n] = { calc_coeff_x<0>(), calc_coeff_x<1>(), calc_coeff_x<2>(), ... }
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];