Variadic expansion to access multi-dimensional std::array - c++

Suppose the following multi_array structure:
template <typename type, std::size_t... sizes>
struct multi_array
{
using storage_type = typename storage_type<type, sizes...>::type;
using value_type = type;
using size_type = std::array<std::size_t , sizeof...(sizes)>;
using difference_type = std::array<std::ptrdiff_t, sizeof...(sizes)>;
using reference = value_type&;
using const_reference = const value_type&;
// ...
storage_type data_ {};
size_type sizes_ {sizes...};
}
// Example usage:
// multi_array<float, 3, 4, 5> three_dimensional_float_array;
// storage_type will be std::array<std::array<std::array<float, 5>, 4>, 3>
// size_type will be std::array<std::size_t, 3>
where:
// Helpers to create a std::array<std::array<...>> from a pack expansion.
template <typename type, std::size_t... sizes>
struct storage_type;
template <typename _type, std::size_t size, std::size_t... sizes>
struct storage_type<_type, size, sizes...>
{
using type = std::array<typename storage_type<_type, sizes...>::type, size>;
};
template <typename _type>
struct storage_type<_type>
{
using type = _type;
};
I am now trying to implement the .at() function:
[[nodiscard]]
constexpr reference at (const size_type& position)
{
// This has to be:
// data_[position[0]][position[1]][position[2]]...;
}
Fold expressions do not work in this case so a recursive solution is necessary.
I think this is the way, but can't seem to come up with the right answer:
[[nodiscard]]
constexpr reference at(const size_type& position)
{
return access_type<value_type, sizeof...(sizes)>::at(data_, position);
}
template <typename type, std::size_t size, std::size_t index = 0>
struct access_type
{
template <typename array_type>
auto at (const array_type& array, const std::array<std::size_t, size>& position, type& current) // Problem: Reference is not a type& for intermediates.
{
if constexpr (index == 0)
current = &array;
if constexpr (index + 1 != size)
{
return access_type::at<type, size, index + 1>(array, position, current);
}
}
};

Something along these lines, perhaps:
template <size_t level, size_t max_level>
struct Access {
template<typename Store>
static reference At(Store& store, const size_type& position) {
return Access<level + 1, max_level>::At(
store[position[level]], position);
}
};
template <size_t level>
struct Access<level, level> {
template<typename Store>
static reference At(Store& store, const size_type&) {
return store;
}
};
reference at(const size_type& position)
{
return Access<0, sizeof...(sizes)>::At(data_, position);
}

Here is my take:
namespace _details
{
template<class InputIt, class OutputIt>
OutputIt partial_product(InputIt first, InputIt last, OutputIt output)
{ *output++ = 1; return partial_sum(first, last, output, std::multiplies<>{}); }
// cache-friendly:
// neighbor objects within the right-most coordinate are neighbors in memory
template<class TDim, class TCoord>
auto coordinates_to_index(TDim const& dimensions, TCoord const& coords)
{
std::array<std::size_t, dimensions.size()> dimension_product;
using std::crbegin, std::crend, std::prev;
partial_product(crbegin(dimensions), prev(crend(dimensions)), begin(dimension_product));
return std::inner_product(cbegin(dimension_product), cend(dimension_product), crbegin(coords), 0);
}
}
It transforms tuple (x,y,z,...) into 1-D index x*N + y*K + ..., where N, K, ... are chosen accordingly to the total container dimensions.
And used in my own multi-dimension cache-friendly container:
/**
* #brief Returns a reference to the element at coordinates.
* #param coordinates Coordinates of the element to return
*
* No bounds checking is performed; if #c coordinates are outside od
* the matrix dimensions, the behavior is undefined.
*/
template<class... Args>
T const& operator()(Args... coordinates) const
{ return _data[_details::coordinates_to_index(dimensions, std::array{coordinates...})]; }
Source : yscialom/matrix/matrix.hpp on my GitHub (PR welcomed)

Related

How to return an Eigen expression that references local variables?

I want to make a function that returns an Eigen expression. Some inputs to this expression are local variables in that function. However as the function returns these local variables go out of scope. Later when the expression is evaluated some garbage data is read producing wrong result.
Simple example:
template<typename T>
auto f(const T& x){
Eigen::ArrayXXd temp = exp(x);
return temp * (1 - temp);
}
live example
EDIT2: Another, more realistic example that is not just element wise operation:
template <typename T>
auto softmax(const T& x){
Eigen::ArrayXXd tmp = exp(x - x.maxCoeff());
return tmp / tmp.sum();
}
Is there a way to extend the lifetime of such a local variable to the lifetime of the expression? Or is there no other way than to evaluate the result before returning it from the function?
EDIT: I am looking for ways to optimize an existing library. So I can not change function signature too much - the return type must be Eigen expression.
Since C++14 you can use init-capture list to store temp as data member of closure generated by lambda expression:
template<typename T>
auto f2(const T& x) {
return [temp = exp(x).eval()](){ return temp * (1-temp); };
}
the syntax of call is funny:
Eigen::ArrayXXd y = f2(x)();
but it works as expected, because temporary object created by f2(x) is destroyed at the end of full expression, so result can be correctly evaluted by y = .. because temp is still alive.
Demo
This can be done by allocating these "local variables" on heap instead. They need to be owned by the returned expression, so the memory is released once the returned expression goes out of scope. To do that we need a custom Eigen expression:
template <class ArgType, typename... Ptrs>
class Holder;
namespace Eigen {
namespace internal {
template <class ArgType, typename... Ptrs>
struct traits<Holder<ArgType, Ptrs...>> {
typedef typename ArgType::StorageKind StorageKind;
typedef typename traits<ArgType>::XprKind XprKind;
typedef typename ArgType::StorageIndex StorageIndex;
typedef typename ArgType::Scalar Scalar;
enum {
Flags = ArgType::Flags & RowMajorBit,
RowsAtCompileTime = ArgType::RowsAtCompileTime,
ColsAtCompileTime = ArgType::ColsAtCompileTime,
MaxRowsAtCompileTime = ArgType::MaxRowsAtCompileTime,
MaxColsAtCompileTime = ArgType::MaxColsAtCompileTime
};
};
} // namespace internal
} // namespace Eigen
template <typename ArgType, typename... Ptrs>
class Holder
: public Eigen::internal::dense_xpr_base<Holder<ArgType, Ptrs...>>::type {
public:
Holder(const ArgType& arg, Ptrs*... pointers)
: m_arg(arg), m_unique_ptrs(std::unique_ptr<Ptrs>(pointers)...) {}
typedef typename Eigen::internal::ref_selector<Holder<ArgType, Ptrs...>>::type
Nested;
typedef Eigen::Index Index;
Index rows() const { return m_arg.rows(); }
Index cols() const { return m_arg.cols(); }
typedef typename Eigen::internal::ref_selector<ArgType>::type ArgTypeNested;
ArgTypeNested m_arg;
std::tuple<std::unique_ptr<Ptrs>...> m_unique_ptrs;
};
namespace Eigen {
namespace internal {
template <typename ArgType, typename... Ptrs>
struct evaluator<Holder<ArgType, Ptrs...>> : evaluator_base<Holder<ArgType>> {
typedef Holder<ArgType, Ptrs...> XprType;
typedef typename nested_eval<ArgType, 1>::type ArgTypeNested;
typedef typename remove_all<ArgTypeNested>::type ArgTypeNestedCleaned;
typedef typename XprType::CoeffReturnType CoeffReturnType;
enum {
CoeffReadCost = evaluator<ArgTypeNestedCleaned>::CoeffReadCost,
Flags = evaluator<ArgTypeNestedCleaned>::Flags
& (HereditaryBits | LinearAccessBit | PacketAccessBit),
Alignment = Unaligned & evaluator<ArgTypeNestedCleaned>::Alignment,
};
evaluator<ArgTypeNestedCleaned> m_argImpl;
evaluator(const XprType& xpr) : m_argImpl(xpr.m_arg) {}
EIGEN_STRONG_INLINE CoeffReturnType coeff(Index row, Index col) const {
CoeffReturnType val = m_argImpl.coeff(row, col);
return val;
}
EIGEN_STRONG_INLINE CoeffReturnType coeff(Index index) const {
CoeffReturnType val = m_argImpl.coeff(index);
return val;
}
template <int LoadMode, typename PacketType>
EIGEN_STRONG_INLINE PacketType packet(Index row, Index col) const {
return m_argImpl.template packet<LoadMode, PacketType>(row, col);
}
template <int LoadMode, typename PacketType>
EIGEN_STRONG_INLINE PacketType packet(Index index) const {
return m_argImpl.template packet<LoadMode, PacketType>(index);
}
};
} // namespace internal
} // namespace Eigen
template <typename T, typename... Ptrs>
Holder<T, Ptrs...> makeHolder(const T& arg, Ptrs*... pointers) {
return Holder<T, Ptrs...>(arg, pointers...);
}
So the function in question can be implemented like this:
template<typename T>
auto f(const T& x){
auto* temp = new Eigen::ArrayXXd(exp(x));
return makeHolder(*temp * (1 - *temp), temp);
}
This is quite complex and has another downside of additional dynamic memory allocations. But it does solve the problem.

Generate Arbitrarily Nested Vectors in C++

I am trying to write a function in order to generate arbitrarily nested vectors and initialize with the given specific value in C++. For example, auto test_vector = n_dim_vector_generator<2, long double>(static_cast<long double>(1), 1); is expected to create a "test_vector" object which type is std::vector<std::vector<long double>>. The content of this test_vector should as same as the following code.
std::vector<long double> vector1;
vector1.push_back(1);
std::vector<std::vector<long double>> test_vector;
test_vector.push_back(vector1);
The more complex usage of the n_dim_vector_generator function:
auto test_vector2 = n_dim_vector_generator<15, long double>(static_cast<long double>(2), 3);
In this case, the parameter static_cast<long double>(2) is as the data in vectors and the number 3 is as the push times. So, the content of this test_vector2 should as same as the following code.
std::vector<long double> vector1;
vector1.push_back(static_cast<long double>(2));
vector1.push_back(static_cast<long double>(2));
vector1.push_back(static_cast<long double>(2));
std::vector<std::vector<long double>> vector2;
vector2.push_back(vector1);
vector2.push_back(vector1);
vector2.push_back(vector1);
std::vector<std::vector<std::vector<long double>>> vector3;
vector3.push_back(vector2);
vector3.push_back(vector2);
vector3.push_back(vector2);
//...Totally repeat 15 times in order to create test_vector2
std::vector<...std::vector<long double>> test_vector2;
test_vector2.push_back(vector14);
test_vector2.push_back(vector14);
test_vector2.push_back(vector14);
The detail to implement n_dim_vector_generator function is as follows.
#include <iostream>
#include <vector>
template <typename T, std::size_t N>
struct n_dim_vector_type;
template <typename T>
struct n_dim_vector_type<T, 0> {
using type = T;
};
template <typename T, std::size_t N>
struct n_dim_vector_type {
using type = std::vector<typename n_dim_vector_type<T, N - 1>::type>;
};
template<std::size_t N, typename T>
typename n_dim_vector_type<T,N>::type n_dim_vector_generator(T t, unsigned int);
template <std::size_t N, typename T>
typename n_dim_vector_type<T, N>::type n_dim_vector_generator<N, T>(T input_data, unsigned int push_back_times) {
if (N == 0)
{
return std::move(input_data);
}
typename n_dim_vector_type<T, N>::type return_data;
for (size_t loop_number = 0; loop_number < push_back_times; loop_number++)
{
return_data.push_back(n_dim_vector_generator<N - 1, T>(input_data, push_back_times));
}
return return_data;
}
As a result, I got an error 'return': cannot convert from 'long double' to 'std::vector<std::vector<long double,std::allocator<long double>>,std::allocator<std::vector<long double,std::allocator<long double>>>>' I know that it caused by if (N == 0) block which is as the terminate condition to recursive structure. However, if I tried to edit the terminate condition into separate form.
template <typename T>
inline T n_dim_vector_generator<0, T>(T input_data, unsigned int push_back_times) {
return std::move(input_data);
}
template <std::size_t N, typename T>
typename n_dim_vector_type<T, N>::type n_dim_vector_generator<N, T>(T input_data, unsigned int push_back_times) {
typename n_dim_vector_type<T, N>::type return_data;
for (size_t loop_number = 0; loop_number < push_back_times; loop_number++)
{
return_data.push_back(n_dim_vector_generator<N - 1, T>(input_data, push_back_times));
}
return return_data;
}
The error 'n_dim_vector_generator': illegal use of explicit template arguments happened. Is there any better solution to this problem?
The develop environment is in Windows 10 1909 with Microsoft Visual Studio Enterprise 2019 Version 16.4.3
To achieve your specific mapping of:
auto test_vector = n_dim_vector_generator<2, long double>(2, 3)
to a 3x3 vector filled with 2's, your template can be a bit simpler if you take advantage of this vector constructor:
std::vector<std::vector<T>>(COUNT, std::vector<T>(...))
Since vector is copyable, this will fill COUNT slots with a different copy of the vector. So...
template <size_t N, typename T>
struct n_dim_vector_generator {
using type = std::vector<typename n_dim_vector_generator<N-1, T>::type>;
type operator()(T value, size_t size) {
return type(size, n_dim_vector_generator<N-1, T>{}(value, size));
}
};
template <typename T>
struct n_dim_vector_generator<0, T> {
using type = T;
type operator()(T value, size_t size) {
return value;
}
};
usage:
auto test_vector = n_dim_vector_generator<2, long double>{}(2, 3);
Demo: https://godbolt.org/z/eiDAUG
For the record, to address some concerns from the comments, C++ has an idiomatic, initializable, contiguous-memory class equivalent of a multi-dimension C array: a nested std::array:
std::array<std::array<long double, COLUMNS>, ROWS> test_array = { /*...*/ };
for (auto& row : test_array)
for (auto cell : row)
std::cout << cell << std::endl;
If you wanted to reduce the boilerplate to declare one, you can use a struct for that:
template <typename T, size_t... N>
struct multi_array;
template <typename T, size_t NFirst, size_t... N>
struct multi_array<T, NFirst, N...> {
using type = std::array<typename multi_array<T, N...>::type, NFirst>;
};
template <typename T, size_t NLast>
struct multi_array<T, NLast> {
using type = std::array<T, NLast>;
};
template <typename T, size_t... N>
using multi_array_t = typename multi_array<T, N...>::type;
Then to use:
multi_array_t<long double, ROWS, COLUMNS> test_array = { /*...*/ };
for (auto& row : test_array)
for (auto cell : row)
std::cout << cell << std::endl;
This is allocated on the stack, like a C array. That will eat up your stack space for a big array of course. But you can make a decorator range around std::unique_ptr to make a pointer to one a bit easier to access:
template <typename T, size_t... N>
struct dynamic_multi_array : std::unique_ptr<multi_array_t<T, N...>> {
using std::unique_ptr<multi_array_t<T, N...>>::unique_ptr;
constexpr typename multi_array_t<T, N...>::value_type& operator [](size_t index) { return (**this)[index]; }
constexpr const typename multi_array_t<T, N...>::value_type& operator [](size_t index) const { return (**this)[index]; }
constexpr typename multi_array_t<T, N...>::iterator begin() { return (**this).begin(); }
constexpr typename multi_array_t<T, N...>::iterator end() { return (**this).end(); }
constexpr typename multi_array_t<T, N...>::const_iterator begin() const { return (**this).begin(); }
constexpr typename multi_array_t<T, N...>::const_iterator end() const { return (**this).end(); }
constexpr typename multi_array_t<T, N...>::const_iterator cbegin() const { return (**this).cbegin(); }
constexpr typename multi_array_t<T, N...>::const_iterator cend() const { return (**this).cend(); }
constexpr typename multi_array_t<T, N...>::size_type size() const { return (**this).size(); }
constexpr bool empty() const { return (**this).empty(); }
constexpr typename multi_array_t<T, N...>::value_type* data() { return (**this).data(); }
constexpr const typename multi_array_t<T, N...>::value_type* data() const { return (**this).data(); }
};
(let the buyer beware if you use those methods with nullptr)
Then you can still brace-initialize a new expression and use it like a container:
dynamic_multi_array<long double, ROWS, COLUMNS> test_array {
new multi_array_t<long double, ROWS, COLUMNS> { /* ... */ }
};
for (auto& row : test_array)
for (auto cell : row)
std::cout << cell << std::endl;
Demo: https://godbolt.org/z/lUwVE_

Dynamically allocated multidimensional array using recursive templates

In order to read and store some results from a MATLAB program, I need to use up to 6 dimensional matrices. Instead of doing something like:
typedef std::vector<double> Row;
typedef std::vector<Row> Matrix2;
typedef std::vector<Matrix2> Matrix3;
typedef std::vector<Matrix3> Matrix4;
typedef std::vector<Matrix4> Matrix5;
typedef std::vector<Matrix5> Matrix6;
I decided to go with templates, and here's what I have so far:
template <class T, int N>
class Matrix {
public:
typedef typename Matrix<T, N - 1>::type MatrixOneDimLower;
typedef std::vector<MatrixOneDimLower> type;
type _data;
template <unsigned int dn, typename ...NT>
Matrix(unsigned int dn, NT ...drest) : _data(dn, MatrixOneDimLower(drest)) {}
MatrixOneDimLower& operator[](unsigned int index)
{
return _data[index];
}
};
template <class T>
class Matrix<T, 1> {
public:
typedef std::vector<T> type;
type _data;
Matrix(unsigned int d0) : _data(d0, T(0.0)) {}
T& operator[](unsigned int index)
{
return _data[index];
}
};
Unfortunately, I'm not very adept in variadic templates and recursive templates, and this doesn't work. For example, if I try to use this as:
Matrix<double, 4> temp(n, dim[2], dim[1], dim[0]);
I get this compile time error (Visual Studio 2017):
error C2661: 'Matrix<double,4>::Matrix': no overloaded function takes 4 arguments
I would really appreciate if you can let me know what I'm doing wrong.
template<class T, std::size_t I>
struct MatrixView {
MatrixView<T, I-1> operator[](std::size_t i) {
return {ptr + i* *strides, strides+1};
}
MatrixView( T* p, std::size_t const* stride ):ptr(p), strides(stride) {}
private:
T* ptr = 0;
std::size_t const* strides = 0;
};
template<class T>
struct MatrixView<T, 1> {
T& operator[](std::size_t i) {
return ptr[i];
}
MatrixView( T* p, std::size_t const* stride ):ptr(p) {}
private:
T* ptr = 0;
};
template<class T, std::size_t N>
struct Matrix {
Matrix( std::array<std::size_t, N> sizes ) {
std::size_t accumulated = 1;
for (std::size_t i = 1; i < sizes.size(); ++i) {
accumulated *= sizes[N-i];
strides[N-i] = accumulated;
}
storage.resize( strides[0] * sizes[0] );
}
MatrixView<T, N> get() { return {storage.data(), strides.data()}; }
MatrixView<T const, N> get() const { return {storage.data(), strides.data()}; }
private:
std::vector<T> storage;
std::array<std::size_t, N-1> strides;
};
this requires doing Matrix<int, 6> m{ {5,4,2,1,3,5} }; to create a matrix with 6 dimensions.
To access it you need to do m.get()[3][0][0][0][0][0] = 4.
You get get rid of that .get() but it is a bit annoying so long as you want to support tensors of first order.
The data is stored contiguously.

Passing variadic parameters in an already template-variadic function

The title is bad but I couldn't come up with anything better. Feel free to change it.
Here's a template multidimensional array class that I'm currently working on. I'm trying to optimise it as much as I can:
#include <array>
template <typename T, std::size_t... Dimensions>
class multidimensional_array
{
public:
using value_type = T;
using size_type = std::size_t;
private:
template<typename = void>
static constexpr size_type multiply(void)
{
return 1u;
}
template<std::size_t First, std::size_t... Other>
static constexpr size_type multiply(void)
{
return First * multidimensional_array::multiply<Other...>();
}
public:
using container_type = std::array<value_type, multidimensional_array::multiply<Dimensions...>()>;
using reference = value_type &;
using const_reference = value_type const&;
using iterator = typename container_type::iterator;
private:
container_type m_data_array;
template<typename = void>
static constexpr size_type linearise(void)
{
return 0u;
}
template<std::size_t First, std::size_t... Other>
static constexpr size_type linearise(std::size_t index, std::size_t indexes...)
{
return multidimensional_array::multiply<Other...>()*index + multidimensional_array::linearise<Other...>(indexes);
}
public:
// Constructor
explicit multidimensional_array(const_reference value = value_type {})
{
multidimensional_array::fill(value);
}
// Accessors
reference operator()(std::size_t indexes...)
{
return m_data_array[multidimensional_array::linearise<Dimensions...>(indexes)];
}
const_reference operator()(std::size_t indexes...) const
{
return m_data_array[multidimensional_array::linearise<Dimensions...>(indexes)];
}
// Iterators
iterator begin()
{
return m_data_array.begin();
}
iterator end()
{
return m_data_array.end();
}
// Other
void fill(const_reference value)
{
m_data_array.fill(value);
}
};
My main function is
int main(void)
{
multidimensional_array<int, 2u, 3u, 4u, 5u, 6u> foo;
int k = 0;
for (auto& s : foo)
s = k++;
//std::cout << foo(0u, 0u, 0u, 1u, 0u) << std::endl;
return 0;
}
The above code compilers without warning/error. As soon as I uncomment the std::cout part though, I get this:
g++-7 -std=c++17 -o foo.o -c foo.cpp -Wall -Wextra -pedantic
foo.cpp: In instantiation of ‘multidimensional_array<T, Dimensions>::value_type& multidimensional_array<T, Dimensions>::operator()(std::size_t, ...) [with T = int; long unsigned int ...Dimensions = {2, 3, 4, 5, 6}; multidimensional_array<T, Dimensions>::reference = int&; multidimensional_array<T, Dimensions>::value_type = int; std::size_t = long unsigned int]’:
foo.cpp:99:37: required from here
foo.cpp:60:72: error: no matching function for call to ‘multidimensional_array<int, 2, 3, 4, 5, 6>::linearise<2, 3, 4, 5, 6>(std::size_t&)’
return m_data_array[multidimensional_array::linearise<Dimensions...>(indexes)];
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~
foo.cpp:38:30: note: candidate: template<class> static constexpr multidimensional_array<T, Dimensions>::size_type multidimensional_array<T, Dimensions>::linearise() [with <template-parameter-2-1> = <template-parameter-1-1>; T = int; long unsigned int ...Dimensions = {2, 3, 4, 5, 6}]
static constexpr size_type linearise(void)
^~~~~~~~~
foo.cpp:38:30: note: template argument deduction/substitution failed:
foo.cpp:60:72: error: wrong number of template arguments (5, should be at least 0)
return m_data_array[multidimensional_array::linearise<Dimensions...>(indexes)];
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~
foo.cpp:44:30: note: candidate: template<long unsigned int First, long unsigned int ...Other> static constexpr multidimensional_array<T, Dimensions>::size_type multidimensional_array<T, Dimensions>::linearise(std::size_t, std::size_t, ...) [with long unsigned int First = First; long unsigned int ...Other = {Other ...}; T = int; long unsigned int ...Dimensions = {2, 3, 4, 5, 6}]
static constexpr size_type linearise(std::size_t index, std::size_t indexes...)
^~~~~~~~~
foo.cpp:44:30: note: template argument deduction/substitution failed:
foo.cpp:60:72: note: candidate expects 2 arguments, 1 provided
return m_data_array[multidimensional_array::linearise<Dimensions...>(indexes)];
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~
Makefile:17: recipe for target 'foo.o' failed
make: *** [foo.o] Error 1
And I know why now. My question is, how can I fix linearise so that it can pass indexes without going through va_list and such? Unfortunately linearise is already a template, variadic function, so I can't use variadic template shenanigans in that regard.
As in preceding question the problem is that the following signatures
template<std::size_t First, std::size_t... Other>
static constexpr size_type linearise(std::size_t index,
std::size_t indexes...)
reference operator()(std::size_t indexes...)
const_reference operator()(std::size_t indexes...) const
aren't what do you mean (indexes a variadic list of std::size_t) but are exactly equivalent to
template<std::size_t First, std::size_t... Other>
static constexpr size_type linearise(std::size_t index,
std::size_t indexes,
...)
reference operator()(std::size_t indexes, ...)
const_reference operator()(std::size_t indexes, ...) const
where indexes is a single std::size_t followed by a C-style optional sequence of argument.
A simple solution (you tagged C++17 but is available starting from C++11) is based on the use of variadic templates.
By example, as follows
template <std::size_t First, std::size_t ... Other, typename ... Ts>
static constexpr size_type linearise (std::size_t index,
Ts ... indexes)
{ return multidimensional_array::multiply<Other...>() * index
+ multidimensional_array::linearise<Other...>(indexes...); }
// Accessors
template <typename ... Ts>
reference operator() (Ts ... indexes)
{ return m_data_array[
multidimensional_array::linearise<Dimensions...>(indexes...)]; }
template <typename ... Ts>
const_reference operator() (Ts ... indexes) const
{ return m_data_array[
multidimensional_array::linearise<Dimensions...>(indexes...)]; }
The following is you're code, modified and compilable
#include <array>
#include <iostream>
template <typename T, std::size_t ... Dimensions>
class multidimensional_array
{
public:
using value_type = T;
using size_type = std::size_t;
private:
template <typename = void>
static constexpr size_type multiply ()
{ return 1u; }
template <std::size_t First, std::size_t ... Other>
static constexpr size_type multiply(void)
{ return First * multidimensional_array::multiply<Other...>(); }
public:
using container_type = std::array<value_type,
multidimensional_array::multiply<Dimensions...>()>;
using reference = value_type &;
using const_reference = value_type const &;
using iterator = typename container_type::iterator;
private:
container_type m_data_array;
template <typename = void>
static constexpr size_type linearise ()
{ return 0u; }
template <std::size_t First, std::size_t ... Other, typename ... Ts>
static constexpr size_type linearise (std::size_t index,
Ts ... indexes)
{ return multidimensional_array::multiply<Other...>() * index
+ multidimensional_array::linearise<Other...>(indexes...); }
public:
// Constructor
explicit multidimensional_array (const_reference value = value_type{})
{ multidimensional_array::fill(value); }
// Accessors
template <typename ... Ts>
reference operator() (Ts ... indexes)
{ return m_data_array[
multidimensional_array::linearise<Dimensions...>(indexes...)]; }
template <typename ... Ts>
const_reference operator() (Ts ... indexes) const
{ return m_data_array[
multidimensional_array::linearise<Dimensions...>(indexes...)]; }
// Iterators
iterator begin ()
{ return m_data_array.begin(); }
iterator end ()
{ return m_data_array.end(); }
// Other
void fill (const_reference value)
{ m_data_array.fill(value); }
};
int main ()
{
multidimensional_array<int, 2u, 3u, 4u, 5u, 6u> foo;
int k{ 0 };
for ( auto & s : foo )
s = k++;
std::cout << foo(0u, 0u, 0u, 1u, 0u) << std::endl;
}
Bonus suggestion.
You tagged C++17 so you can use "folding".
So you can substitute the couple of multiply() template functions
template <typename = void>
static constexpr size_type multiply ()
{ return 1u; }
template <std::size_t First, std::size_t ... Other>
static constexpr size_type multiply ()
{ return First * multidimensional_array::multiply<Other...>(); }
with a single folded one
template <std::size_t ... Sizes>
static constexpr size_type multiply ()
{ return ( 1U * ... * Sizes ); }
My approach is similar to that in this answer, except that instead of using std::tuple to store a list of types, I define my own type size_t_pack to store a (compile-time) list of size_t's.
using std::size_t;
template<size_t... values>
struct size_t_pack{};
template<size_t first_value,size_t... rest_values>
struct size_t_pack<first_value,rest_values...>{
static constexpr size_t first=first_value;
using rest=size_t_pack<rest_values...>;
static constexpr size_t product=first*rest::product;
};
template<>struct size_t_pack<>{
static constexpr size_t product=1;
};
Defines members: first, rest (in case not empty) and product (since it's not possible to specialize a function using the templates of a template argument, as far as I know, another choice is to if constexpr and make the type support checking for empty)
With that, it's easy to define the linearize function:
template<class dimensions,class... SizeTs>
static constexpr size_type linearise(std::size_t index, SizeTs... indices)
{
using restDimensions=typename dimensions::rest;
return restDimensions::product *index +
multidimensional_array::linearise<restDimensions>(indices...);
}
Using a std::tuple to store the list of types (SizeTs) is also possible, although struct partial specialization is still required, as far as I know.
You need to make indexes a parameter pack by making the operator() function a template, and expand the parameter pack when you use it by putting ... afterwards:
template <class... DimensionType>
const_reference operator()(DimensionType... indexes) const
{
return m_data_array[multidimensional_array::linearise<Dimensions...>(indexes...)];
}
See: parameter pack expansion
The code still will not compile because of a similar problem in linearize(), but that gets you on the right track.

Defining conversion operator for specialized template class only

I want to define conversion to float for matrix<1, 1>. I have trouble figuring out how to actually define it. If I make it a global function
template<typename T>
inline operator T(const matrix<T, 1, 1> &m){ return m(0, 0); }
I get "operator.. must be a non static member function"
I can of course define it as member for the generic matrix template but then it will be defined for all matrices - which is not what I want. I want it to be defined only for the specific case of 1x1 matrix.
You have to specialize a class for that, for example:
template <typename Base, typename T, std::size_t W, std::size_t H>
struct MatrixConversion
{ /*Empty*/ };
template <typename Base, typename T> struct MatrixConversion<T, 1u, 1u>
{
operator const T&() const { return static_cast<const Base&>(*this).m[0][0]; }
};
template <typename T, std::size_t W, std::size_t H>
struct Matrix : MatrixConversion<Matrix<T, W, H>, T, W, H>
{
// Your code
};
composition plus specialisation would be the most maintainable approach.
You did not specify the number of dimensions in your matrix template class, so I have assumed it can be variadic.
#include <cstdint>
#include <utility>
//
// forward-declare class template for convenience.
//
template<class T, std::size_t...Dimensions>
struct matrix;
//
// classes to figure out the storage requirements of a multi-dimensional
// matrix
//
template<class T, std::size_t...Dimensions> struct storage;
template<class T, std::size_t N>
struct storage<T, N>
{
using type = T[N];
};
template<class T, std::size_t...Rest, std::size_t N>
struct storage<T, N, Rest...>
{
using less_dimension_type = typename storage<T, Rest...>::type;
using type = less_dimension_type[N];
};
//
// functions for dereferencing multi-dimensional arrays
//
template<class Array, class Arg>
decltype(auto) deref(Array& array, Arg&& arg)
{
return array[arg];
}
template<class Array, class Arg, class Arg2>
decltype(auto) deref(Array& array, Arg&& arg, Arg2&& arg2)
{
return array[arg][arg2];
}
template<class Array, class Arg, class...Args>
decltype(auto) deref(Array& array, Arg&& arg, Args&&...args)
{
return deref(deref(array, arg), std::forward<Args>(args)...);
}
//
// prototype for operations we want to conditionally apply
//
template<class Matrix>
struct matrix_conditional_ops
{
// in the general case, none
};
//
// compose the matrix class from conditional_ops<>
//
template<class T, std::size_t...Dimensions>
struct matrix
: matrix_conditional_ops<matrix<T, Dimensions...>>
{
template<class...Dims>
decltype(auto) at(Dims&&...ds)
{
return deref(_data, std::forward<Dims>(ds)...);
}
template<class...Dims>
decltype(auto) at(Dims&&...ds) const
{
return deref(_data, std::forward<Dims>(ds)...);
}
typename storage<T, Dimensions...>::type _data;
};
//
// define the condition operations for the <T, 1, 1> case
//
template<class T>
struct matrix_conditional_ops<matrix<T, 1, 1>>
{
using matrix_type = matrix<T, 1, 1>;
operator T const() { return static_cast<matrix_type const&>(*this).at(0,0); }
};
int main()
{
matrix<double, 1, 1> m11;
m11.at(0,0) = 6.0;
double d = m11;
matrix<double, 2, 2> m22;
// compile error:
// double d2 = m22;
// bonus points:
matrix<double, 3, 5, 2, 7> mxx;
mxx.at(2, 4, 1, 6) = 4.3; // probably needs some compile-time checking...
}
someone may want to check my logic for the array packing/dereferencing...
Jarod and Richard already gave you the best answers in my opinion, they scale well to any number of operators with all kinds of restrictions.
However, if you cannot afford to redesign your class, or all you need is a quick and dirty opertor T() you can get away with the following
template<typename T, std::size_t N1, std::size_t N2>
struct Matrix
{
T m[N1][N1];
operator T()
{
static_assert(N1 == 1 && N2 == 1, "Only applicable to scalars");
return m[0][0];
}
};
Which is live here.