Here's my solution, using C++11 variadic templates and pack expansions.
My "foobar" compiles as a constexpr function so I'd say that's pretty efficient :p
As for elegance, you can be the judge, but I think it's pretty good.
Basically the idea is to replace for loops with a functional programming way of doing iteration, where we just seek to explicitly build the set we want to iterate over first. That code can then be generalized to arbitrary dimensionality in a more straightforward way.
The code is totally self-contained save for <array> header.
It compiles for me at C++11 standard with gcc 4.8.2 and clang 3.6.
Note: If you want to use the same technique for code where the dimensions are a run-time parameter, basically what you would do is reimplement the Cartesian_Product metafunction below as a run-time function using something like std::vector<std::vector<size_t>>. Then you can build your iteration set by taking dim number of cartesian products, and iterate over that using a single for loop to populate the result.
#include <array>
#include <iostream>
/////////////////////////////////////////////
// The point class from problem statement
/////////////////////////////////////////////
template <size_t dims> class Point { public: double data[dims]; };
// Some basic type list and meta function objects to work with
// List of indices
template<size_t... sl>
struct StList {
static constexpr size_t length = sizeof...(sl);
};
// Metafunction to compute a volume
template <typename T>
struct Compute_Volume;
template<size_t s>
struct Compute_Volume<StList<s>> {
static constexpr size_t value = s;
};
template <size_t s1, size_t s2, size_t... sl>
struct Compute_Volume<StList<s1, s2, sl...>> {
static constexpr size_t value = s1 * Compute_Volume<StList<s2, sl...>>::value;
};
// Concatenate
template<typename TL, typename TR>
struct Concat;
template<size_t... SL, size_t... SR>
struct Concat<StList<SL...>, StList<SR...>> {
typedef StList<SL..., SR...> type;
};
template<typename TL, typename TR>
using Concat_t = typename Concat<TL, TR>::type;
// Meta function to check if a typelist is component-wise less than another typelist
// For testing purposes
template <typename TL, typename TR>
struct all_less_than;
template <size_t l1, size_t... SL, size_t r1, size_t... SR>
struct all_less_than<StList<l1, SL...>, StList<r1, SR...>> {
static constexpr bool value = (l1 < r1) && all_less_than<StList<SL...>, StList<SR...>>::value;
};
template<>
struct all_less_than<StList<>, StList<>> {
static constexpr bool value = true;
};
/////////////////////////////////////////////////////////////////////////////
// constexpr template function for the point initializations you described
/////////////////////////////////////////////////////////////////////////////
template <typename index, typename dims>
struct point_maker;
template <size_t... idx, size_t... dim>
struct point_maker<StList<idx...>, StList<dim...>> {
static_assert(sizeof...(idx) == sizeof...(dim), "misuse of 'point_maker' template, idx and dim must have same number of coordinates");
static_assert(all_less_than<StList<idx...>, StList<dim...>>::value, "misuse of 'point_maker' template, idx is out of bounds");
static constexpr Point<sizeof...(idx)> make_point() {
return {{ ((idx + 0.5) / static_cast<double>(dim))... }};
}
};
//////////////////////////////////////////////////////////////////////////////////////////
// Now we need to abstract the for loops. We need a little more infrastructure for this.
//////////////////////////////////////////////////////////////////////////////////////////
// A basic typelist object
template <typename... tl>
struct TypeList {
static constexpr size_t length = sizeof...(tl);
};
// Specialization for the Concat metafunction
template<typename... TL, typename... TR>
struct Concat<TypeList<TL...>, TypeList<TR...>> {
typedef TypeList<TL..., TR...> type;
};
// Metafunction to compute the cartesian product of two lists of lists, and evaluate the `Concat` metafunction for each pair.
template <typename S, typename T>
struct Cartesian_Product;
template <typename s1, typename s2, typename... sl, typename T>
struct Cartesian_Product<TypeList<s1, s2, sl...>, T> {
typedef Concat_t<typename Cartesian_Product<TypeList<s1>, T>::type, typename Cartesian_Product<TypeList<s2, sl...>, T>::type> type;
};
template <typename s, typename t1, typename t2, typename... tl>
struct Cartesian_Product<TypeList<s>, TypeList<t1, t2, tl...>> {
typedef Concat_t<typename Cartesian_Product<TypeList<s>, TypeList<t1>>::type, typename Cartesian_Product<TypeList<s>, TypeList<t2, tl...>>::type> type;
};
template <typename s, typename t>
struct Cartesian_Product<TypeList<s>, TypeList<t>> {
typedef TypeList<Concat_t<s, t>> type;
};
template <typename S, typename T>
using Cartesian_Product_t = typename Cartesian_Product<S, T>::type;
// Some unit tests for the above :)
static_assert( std::is_same<Cartesian_Product_t<TypeList<StList<1>, StList<2>>, TypeList<StList<3>, StList<4>>>, TypeList<StList<1,3>, StList<1,4>, StList<2,3>, StList<2,4>>>::value , "cartesian product not working");
static_assert( std::is_same<Cartesian_Product_t<TypeList<StList<1,5>, StList<2>>, TypeList<StList<3>, StList<4>>>, TypeList<StList<1,5,3>, StList<1,5,4>, StList<2,3>, StList<2,4>>>::value , "cartesian product not working");
static_assert( std::is_same<Cartesian_Product_t<TypeList<StList<1,5>, StList<2>>, TypeList<StList<>>>, TypeList<StList<1,5>, StList<2>>>::value , "cartesian product not working");
// Metafunction to count from 0 to n
// Count from zero to n-1, and make a sequence of singleton sets containing the numbers
template<size_t s>
struct Count {
typedef Concat_t<typename Count<s-1>::type, TypeList<StList<s-1>>> type;
};
template<>
struct Count<0> {
typedef TypeList<> type;
};
template<size_t s>
using Count_t = typename Count<s>::type;
// Metafunction to abstract a series of for loops, generating the list of all the index tuples the collection of loops would generate
template <typename T>
struct Volume_Maker;
template <>
struct Volume_Maker<StList<>> {
typedef TypeList<StList<>> type;
};
template <size_t s, size_t... sl>
struct Volume_Maker<StList<s, sl...>> {
typedef Cartesian_Product_t<Count_t<s>, typename Volume_Maker<StList<sl...>>::type> type;
};
template <typename T>
using Volume_t = typename Volume_Maker<T>::type;
// Some more quick unit tests
template <typename T>
struct Volume_Test {
static_assert( Volume_t<T>::length == Compute_Volume<T>::value, "volume computation mismatch");
};
Volume_Test<StList<1,2,3>> test1{};
Volume_Test<StList<1,1,1>> test2{};
Volume_Test<StList<1>> test3{};
Volume_Test<StList<7,6,8>> test4{};
/////////////////
// Grand finale
/////////////////
template <typename dim_list, typename idx_list = Volume_t<dim_list>>
struct foobar_helper;
template <size_t... dim, typename... idx>
struct foobar_helper<StList<dim...>, TypeList<idx...>> {
typedef StList<dim...> dim_list;
typedef std::array<Point<sizeof...(dim)>, sizeof...(idx)> array_type;
static constexpr array_type make_array() {
return {{ point_maker<idx, dim_list>::make_point()... }};
}
};
template <size_t... dim>
constexpr std::array<Point<sizeof...(dim)>, Compute_Volume<StList<dim...>>::value> foobar() {
return foobar_helper<StList<dim...>>::make_array();
}
/////////////////
// Example usage
/////////////////
template <size_t ndim>
std::ostream & operator << (std::ostream & s, const Point<ndim> & p) {
s << "[ ";
for (size_t i = 0; i < ndim; ++i) {
if (i) { s << ", "; }
s << p.data[i];
}
s << " ]";
return s;
}
template<size_t ndim, size_t N>
void print_array(std::ostream & s, const std::array<Point<ndim>, N> & a) {
for (size_t i = 0; i < N; ++i) {
s << a[i] << std::endl;
}
}
int main() {
constexpr auto array1 = foobar<2,2,2>();
print_array(std::cout, array1);
constexpr auto array2 = foobar<3,3,3,3>();
print_array(std::cout, array2);
}
point.data[i] = (v[i]+0.5) / (double)counts[i];? I think I don't understand what is "its multidimensional grid points".x,y, andz(normalized to [0,1])--that is, their position within the multidimensional array.