How do you statically generate floating point data at compile time?

Given that I want to perform filtering on some data, how can I avoid generating this data at runtime, but I maintain the flexibility of resizing and distributing the data of these filters, while also maintaining a nice clean reusable code. I know that I can use templates to do something like the following:

template <int x> class Filter { static const float f; static const Filter<x-1> next; inline float* begin(const Filter<x>& el){ return &f; } inline float* end(const Filter<x>& el) { return (&f)+x+1; } }; template <> class Filter<0> { static const float f; inline float* begin(const Filter<0>& el){ return &f; } inline float* end(const Filter<0>& el) { return (&f)+1; } }; template <int x> const float Filter<x>::f = someDistribution(x); template <> const float Filter<0>::f = someDistribution(0); 

This will actually generate data in my filter according to the index x in the filter object according to someDistribution (...). However, there are some drawbacks to my use ...

1) I think that I am right in saying that although this data is not generated when the object is built, it is generated once when the program starts. - I could tolerate this, although I would prefer that the filter be computed in comiletime and baked then and there (is this even possible for floating-point data?)

2) The filter will not create an instance of the "next" element if there is no member function (called somewhere!) That traverses the length of the structure, i.e.

 // inside template<int x> class Filter inline void instantiate() const { next.instantiate(); }; // then inside template<> class Filter<0> inline void instantiate() const { }; 

I have to do it wrong in order to require the plunging installation function, and this does not allow to execute the easily supported sentence.

edit: the reason I care here is because I would like to make sure the next members are created, so I can traverse the static "array" using the start and end functions.

So, firstly, how can I fix problem 2 and get rid of the instance function, and secondly, I can fix problem 1 so that this data is dynamically generated during compilation and supported.

(NB I, for similar problems, I used python pre-compilation scripts to generate source files containing filter data, but I do not want to use this here, since this is his own fish maker!)

+6
source share
2 answers

Since you cannot use contexpr ... As for your questions:

  • If you are not looking for the next largest prime number, you should not worry that simple calculations are performed once at startup. Try to measure it, and you will probably find that initialization takes less than a millisecond.

    Thus, the values โ€‹โ€‹computed at startup behave like variables (must be asked for their values โ€‹โ€‹each time they are used), while the constant value of compilation time is always known. Consequently, the former may be slightly slower, but probably without any meaning.

    Always measure first before introducing inconvenience.

  • Again, why does this bother you? If the Filter type for a particular x not used anywhere in the code, why should the value be somewhere there?

    Static templates are problematic if they depend on each other - in your case they do not, each f is autonomous.

Having said all this, a great tool for messing around - http://gcc.godbolt.org/ - you see the assembly as you type. It does NOT support MS compilers, but it gives you a pretty good idea of โ€‹โ€‹how compilers optimize material.

If your disrtibution is simple enough to be a macro, this will be a compile-time constant:

 #define someDistribution(x) x * x template <int x> struct Filter { static const float f; }; template <int x> const float Filter<x>::f = someDistribution(x); int main() { return Filter<200>::f + Filter<100>::f; } 

Assembly (Clang):

 main: # @main movl $50000, %eax # imm = 0xC350 ret 

If you change someDistribution to a function, even a built-in, you will see that the calculation should take place.

EDIT: Remember that you can do maaany with macros, including "specializing" in certain values. Simple distribution should be convenient for the preprocessor.

+2
source

You can get one piece of the puzzle using variable patterns. Once integer_sequence support is added to the standard library, you can use it, not seq / gen_seq.

 #include <array> #include <iostream> using namespace std; template<size_t... Is> struct seq {}; template<size_t N, size_t... Is> struct gen_seq : gen_seq<N - 1, N - 1, Is...> {}; template <size_t... Is> struct gen_seq<0, Is...> : seq<Is...>{}; template<typename Func, size_t... Is> const array<float, sizeof...(Is)>& make_coeffs(Func f, seq<Is...>) { static const auto coeffs = array<float, sizeof...(Is)>{ f(Is)... }; return coeffs; } float square(float x) { return x * x; } int main() { const auto coeffs = make_coeffs(square, gen_seq<10>{}); for (float x : coeffs) { cout << x << " "; } cout << endl; } 

To make this compile time, not initialized at startup, although you really want constexpr support not to be supported by VS 2013. Here is the constexpr version :

 #include <array> #include <iostream> using namespace std; template<size_t... Is> struct seq {}; template<size_t N, size_t... Is> struct gen_seq : gen_seq<N - 1, N - 1, Is...> {}; template <size_t... Is> struct gen_seq<0, Is...> : seq<Is...>{}; template<typename Func, size_t... Is> constexpr array<float, sizeof...(Is)> make_coeffs(Func f, seq<Is...>) { return array<float, sizeof...(Is)>{ f(Is)... }; } constexpr float square(float x) { return x * x; } int main() { constexpr auto coeffs = make_coeffs(square, gen_seq<10>{}); static_assert(coeffs[3] == 9, ""); for (float x : coeffs) { cout << x << " "; } cout << endl; } 
-1
source

Source: https://habr.com/ru/post/957197/


All Articles