简体   繁体   中英

Lazy initialization of a static member array of a template class

I am writing code to perform Gaussian integration with n points, where n is a compile time constant.

For a given n , I know how to compute abscissas and weights. The computation has to be done from scratch for each different n .

Now, I do something along these lines:

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

to be used like this:

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

Now, I can specialize in a translation unit the coefficients for legendre<12> , by doing

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

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

and everything is fine, as long as I only use 12-points Gauss-Legendre.

Now, I'm experimenting with different number of points, and I know how to generate the weights and nodes. I can for instance provide a routine

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

and :

  • When I call gauss_quadrature<legendre<n>> , the template legendre<n> is automatically instantiated (this is the case).
  • When legendre<n> is instantiated for some compile-time n , I'd like the above compute_legendre_coeffs to be called at some point before main so that it fills the x and w member arrays. How do I achieve this ?

I know must define the arrays first:

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

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

but I can't come up with a method to initialize them. Anyone has a trick to do so ?

Convert the arrays to 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>();

This does mean computing the x and w coefficients separately, though there are workarounds if this is less efficient, for example:

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());

First of all: you can't do initialization completely at compile-time using C++03 (yep, by design!) -- the only way to do it is to use C++ templates, but you'll unable to pass a double as template parameter.

Things getting better w/ C++11 -- you may use constexpr but only if your compute_legendre_coeffs() is trivial enough.

Or there is one trick I use when need to take some actions by fact of class declaration -- for example, register smth somewhere... to provide serialization capabilities via some library or smth like this.

You may use static constructors idiom to initalize that arrays... For simialr reasons I use the following code:

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;

then you have to inherit static_xtors class, and implement two static methods: void static_ctor() -- which would initialize your arrays, and empty (in your case) void static_dtor() ... Ie smth like this:

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

As you may notice, x and w is not const anymore :( -- you may try to make them const again hiding to private and add static getters to be used by callers... Also, your internal arrays would be initialized at runtime, but before the main function (and just once)...


or play w/ constexpr ... but seems you'll need to redesign you initializer function ( somehow ) because using initialization lists it should looks like this:

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

... and definitely you can't do it w/o specializing (and extensive macros usage). But probably variadic templates may help... need to know more details about your function and time to think :))

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

By providing an accessor you gain control of the entry point to your class. The accessors dispatch to a init function that uses initialization of a local static variable to call do_init only once in the program lifetime. The do_init does the actual initialization of the members.

Notes:

Depending on the compiler, this might not be thread safe (ie not all C++03 compilers provide thread safe initialization of static variables, which in turn means that the do_init might be called more than once in parallel, depending on the algorithm that might or not be an issue --ie if do_init computes the values aside and just writes them, the potential race condition is irrelevant as the net result will be the same). Some compilers offer mechanisms to guarantee one off execution (I believe boost has such a mechanism). Alternatively depending on your domain, you might be able to prime the coefficients before you start the threads.

The actual arrays cannot be const in this case, as the initialization needs to happen after they are created. That should not be an issue for anything other than possible micro optimizations (ie the compiler does not know about the values of the coefficients, so it cannot perform sub-expression evaluation at compile time).

Maybe you can try turning your function into an initializer class template, whose parameter would be the class/struct you want to initialize. Then change that class template to include a constant instance of the initializer . Finally, have the constructor of the initializer class trigger the code doing the actual initialization.

You should perhaps also protect access [1] to the initializer class so that the initialization doesn't happen more than once.

The idea, as you can see, is to use the facts that class instances get their constructor code called, template instances get their constant data initialized.

Below is a possible (and simple) implementation, without template:

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

Here's another take on it, this time putting the initialization in the struct directly:

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

The interesting characteristic of this solution is that the T_init constant instance shouldn't take any space within the T struct. The initialization logic is bundled with the class that needs it, and the T_init template only enables it automatically.

[1] Xeo mentioned the std::call_once template, which could come handy here.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM