5

I have a template class like this:

template <unsigned N>
class Pixel {
    float color[N];
}

I hope to have a constructor with exact N parameters to initialize the array in the class, like this:

Pixel<N> (float x_1, float x_2, ..., float x_N) {
    color[0] = x_1;
    color[1] = x_2;
    ...
}

Obviously I can't implement the constructor by hand for each N. So how can I achieve this goal by template metaprogramming or any other techniques?

iammilind
  • 68,093
  • 33
  • 169
  • 336
Oliver Q
  • 123
  • 1
  • 4

3 Answers3

10

The other answers are good and practical, but the question is interesting, and the technique behind doing something like that can form a good basis for similar, but more complicated and/or practical problems and solutions. Here's something that counts the constructor arguments the way you describe:

template <unsigned int N>
class Pixel {
public:
    template<typename... Floats> //can't use float... anyway
    Pixel(Floats&&... floats) : color{std::forward<Floats>(floats)...} {
        static_assert(sizeof...(Floats) == N, "You must provide N arguments.");
    }

private:
    float color[N];
};

int main() {
    Pixel<3> p(3.4f, 5.6f, 8.f);   
    Pixel<3> p2(1.2f); //static_assert fired
}
jrok
  • 54,456
  • 9
  • 109
  • 141
chris
  • 60,560
  • 13
  • 143
  • 205
  • It's [Variadic template](http://en.wikipedia.org/wiki/Variadic_templates). Thank you, chris, you let me know this feature for the first time. probably I need go to learn new features of C++11. – Oliver Q Jul 13 '13 at 05:45
  • @OliverQ, They're quite interesting and useful to look into and play around with. Variadic templates are one of the harder ones to understand and use. I generally suck at using them properly for metaprogramming purposes. – chris Jul 13 '13 at 05:47
2

I would make use of std::array like so:

#include <array>
#include <iostream>

template<unsigned int N>
class Pixel
{
public:
    Pixel(std::array<float, N> values)
    {
        for(size_t i=0; i<N; i++)
        {
            colors[i] = values[i];
        }
    }

private:
    float colors[N];
};

int main(int argc, char* argv[])
{
    std::array<float, 5> array = { 0.0f, 1.1f, 2.2f, 3.3f, 4.4f };
    Pixel<5> p(array);

    return 0;
}

I used float colors[N]; as the member variable because that's what it seemed like you had, but if it were up to me I'd just store the array itself. If you don't have access to a c++11 compiler there may be a way to get a similar result using boost::tuple (chris informs me that std::tuple is also c++11, oops).

Borgleader
  • 15,826
  • 5
  • 46
  • 62
  • `std::tuple` is still C++11, by the way. There's always `boost::array` and `boost::tuple`, though. – chris Jul 12 '13 at 04:14
2

A lot here depends on where you're starting from (C++03 vs. C++11) and where you really want to go (passing just numbers, or if passing something like an std::array works for you).

If you have C++11 and you just want to pass the numbers, it's probably easiest to do something like:

#include <vector>
#include <iostream>
#include <initializer_list>

class pixel {
    std::vector<double> color;
public:
    pixel(std::initializer_list<double> && l) : color(l) {}
    ~pixel() {
        // show the data we received:
        for (double const &f : color)
            std::cout << f << "\t";
    }
};

int main() {
    pixel{1.9, 2.8, 3.7, 4.6, 5.5};
}

Note that an std::initializer_list doesn't support narrowing conversions, so if you want to store the numbers as float instead of double, you'll need to actually pass floats:

pixel{1.9f, 2.8f, 3.7f, 4.6f, 5.5f};

Unlike @Chris's solution, however, this does not attempt to enforce passing a given number of arguments -- it just conforms to storing whatever number you pass. In return for that, it's a bit easier to use. You don't need to specify the size -- it figures that out from the number of items you pass.

If you like that general idea, but insist on an array and C++03 (why?) you can do something like this:

#include <vector>
#include <iostream>
#include <algorithm>

template<class T, size_t N>
class pixel {
    T color[N];
public:
    pixel(T(&matrix)[N]) {
        std::copy_n(matrix, N, color);
    }
};

template <class T, size_t N>
pixel<T, N> make_pixel(T(&matrix)[N]) {
    return pixel<T, N>(matrix);
}

int main() {
    float a [] = { 1.1, 2.2, 3.3, 4.4, 5.5 };
    pixel<float, 5> p = make_pixel(a);
    return 0;
}

In this case, I've passed float as a template parameter, but if you're really sure it'll always be float, you can just pass the size, and use float instead of T.

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
  • Indeed, I was going for doing what was described over, perhaps, something more practical. It's at the very least a good example for other, more complicated, things. Good answer. – chris Jul 12 '13 at 04:51
  • Good answer, but instead of `std::vector` you could have used `std::array`. – iammilind Jul 12 '13 at 05:11
  • @iammilind: Yes -- could have, but borgleader already showed that, and under the circumstances, it's not clear it's necessarily the only (or even best) choice. That's not to say it's a bad idea either, just that without knowing the usage, it's hard to say. – Jerry Coffin Jul 12 '13 at 05:15
  • Under any circumstance, if one has to make a choice between `std::array` and `std::vector`, it has to be `std::array`. Not only the performance-wise, but also the readability wise, the reader of the code will be assured that the size is never going to change. Since here the size is known at compile time, it's better to have `std::array`. Just my thougt :) – iammilind Jul 12 '13 at 05:25
  • @iammilind: Not the only consideration, by any means. Just for example, the maximum size of an `std::array` is often quite limited compared to a `std::vector`. – Jerry Coffin Jul 12 '13 at 05:28