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 the compile-time constant.

For a given n I know how to calculate abscissas and weights. The calculation must be performed from scratch for every other n .

Now I am doing something in this direction:

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

for use as follows:

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

Now I can specialize in translation units odds 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 use only the 12-position Gauss Legendre.

Now I am experimenting with a different number of points, and I know how to create weights and knots. I can, for example, provide a routine

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

and:

  • When I call gauss_quadrature<legendre<n>> , the legendre<n> template is automatically created (this is the case).
  • When legendre<n> is created for some compilation time n , I would like the aforementioned compute_legendre_coeffs called at some point before main so that it fills the x and w member arrays. How to achieve this?

I know I must first define arrays:

 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. Does anyone have a trick?

+4
source share
4 answers
 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 accessory, you gain control over the entry point into your class. Accessors are sent to the init function, which uses the initialization of a local static variable to call do_init only once during the program life cycle. do_init does the actual initialization of the members.

Notes:

Depending on the compiler, this may not be thread safe (i.e., not all C ++ 03 compilers provide stream safe initialization of static variables, which, in turn, means that do_init can be called more than once in parallel, depending on the algorithm , which may or may not be a problem - if do_init calculates the values ​​to the side and just writes them, the potential state of the race does not matter, since the net result will be the same). Some compilers offer mechanisms that guarantee one execution (I believe that boost has such a mechanism). Alternatively, depending on your domain, you can transfer the odds before starting the streams.

In this case, the actual arrays cannot be const , since initialization must occur after they are created. This should not be a problem for anything other than possible microoptimizations (i.e., the Compiler is not aware of the values ​​of the coefficients, so it cannot evaluate subexpressions at compile time).

+1
source

Convert 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 means calculating the coefficients x and w separately, although there are workarounds if it 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()); 
+2
source

First of all: you cannot completely initialize at compile time with C ++ 03 (yes, by design!) - the only way to do this is to use C ++ templates, but you cannot pass a double parameter to the template.

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

Or there is one trick that I use when I need to take some action upon the declaration of a class - for example, register somewhere somewhere ... in order to provide serialization capabilities through some library or smth like this.

You can use static idiom constructors to initialize these 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 need to inherit the static_xtors class and implement two static methods: void static_ctor() - which initializes 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 can see, x and w no longer constants :( - you can try to make them const hidden again to private and add static getters that will be used by callers ... In addition, your internal arrays will be initialized at runtime, but before the main function (and only once) ...


or play w / constexpr ... but it looks like you will need to redesign the initialization function (one way or another) because, using the initialization lists, it should look 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, of course, you cannot do this without specialization (and using advanced macros). But probably, variable templates can help ... you need to know more details about your function and time to think :))

+1
source

Perhaps you can try turning your function into an initializer class template, the parameter of which will be the class / structure that you want to initialize. Then modify this class template to include a persistent initializer instance. Finally, the initializer class constructor runs the code that does the actual initialization.

Perhaps you should also protect access [1] to the initializer class so that the initialization does not repeat more than once.

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

The following is a possible (and simple) implementation without a 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 we also take it, this time putting the initialization in the structure 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); } }; 

An interesting characteristic of this solution is that the T_init constant instance T_init not occupy a space inside the T structure. The initialization logic comes with the class that it needs, and the T_init template T_init allows it automatically.

[1] Xeo mentioned the std :: call_once pattern, which may come in handy here.

0
source

All Articles