Question

Given I want to perform filtering on some data, how can I avoid generating this data at runtime, but maintain the flexibility of changing sizes and data distribution of these filters, while also keeping 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 indeed generate data in my filter according to index x in the filter object according to someDistribution(...). However there are some downsides for my useage...

1) I think I'm right in saying that while this data is not generated on object construction it is generated once on program start. - this I could tolerate although would rather the filter be computed at comiletime and baked in then and there (is this even possible for float data?)

2) The filter won't instantiate the "next" member unless there is a member function (that is called somewhere !) which 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 must be doing it wrong to require the plunging instantiation function and this fails the easily maintainable clause.

edit: the reason I care here is that I would like to make sure that the next members are instantiated so i can traverse the static 'array' using the begin and end functions.

So firstly how do I fix problem 2 and do away with the instantiate function, and secondly is it possible to fix problem 1 so that this data is dynamically generated in compile time and backed in.

(NB I on similar problems I have used python pre-compilation scripts to generate source files containing the filter data, but I don't want to use that here as that is its own kettle of fish!)

Was it helpful?

Solution

Since you can't use contexpr... Regarding your questions:

  1. Unless you are searching for the next biggest prime number, you shouldn't be bothered that simple calculations take place once at startup. Try to measure it and it is likely that you discover that the initialisation is done in less than a milisecond.

    That said, values computed at startup behave as variables (have to be asked for their value everytime they are used), whereas compile-time constant's value is always known. Hence the former might be a bit slower, but likely without any significance.

    Always measure first before introducing an inconvenience.

  2. Again, why do you care? If Filter type for particular x is not used anywhere in the code, why should the value be around there somewhere?

    Template statics are problematic if they depend on each other - in your case they don't, each f is autonomonous.

Having said all that, a great tool to tinker around is http://gcc.godbolt.org/ - you see the assembly as you type. It does NOT support MS compilers, but it gives you a pretty good guess about how compilers optimise stuff away.

If your disrtibution is simple enough to be a macro it 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;
}

The assembly (Clang):

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

If you change someDistribution to be a function, even inline one, you'll see that computation will have to take place.

EDIT: remember, that you can do maaany thing with macros, including "specialising" them for certain values. Simple distribution should be preprocessor-friendly.

OTHER TIPS

You can get one part of the puzzle using variadic templates. Once integer_sequence support is added to the standard library you could use that rather than 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 rather than initialized at startup though you really want constexpr support which VS 2013 doesn't have. Here's 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;
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top