6

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!)

geoff3jones
  • 605
  • 1
  • 7
  • 17
  • 2
    I don't think your generic `end` function is legal. Adding more than 1 to the address of a non-array variable should be illegal. – Mark B Nov 01 '13 at 15:44
  • so this should be ok as the `float f` should all be contiguous in memory allowing you to take a pointer to the first and iterate to the end. Importantly the end iterator is required to be dereferenceable and it is legal to hold a pointer to uninitialised memory. I agree that I could have been more clear by writing `((float*)(&f)) + x + 1` however I feel that the meaning of the rest of code snippet is more clear with the short form used above. Thanks for checking though. – geoff3jones Nov 05 '13 at 15:12

2 Answers2

2

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.

gwiazdorrr
  • 6,181
  • 2
  • 27
  • 36
  • Thanks, this answers my question and points me in the right direction as well. Also with regard to "why?" on part two I think I was ambiguous (and will correct above) but I would like to be able to use the begin() end() to access the data but if all the `next` members have not been instantiated this of course creates junk. OH and you tinker tool link - awesome! :) – geoff3jones Nov 05 '13 at 14:57
-1

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;
}
mattnewport
  • 13,728
  • 2
  • 35
  • 39