How to design a matrix in C++ - c++

I wanted to design a matrix and wanted that it is not possible to initialize it bad. Consider for example this:
template <typename T, unsigned int R, unsigned int C>
class matrix {
array<T,R*C> _data;
//... Constructors, etc. ...
};
Now I would like to have a design where the following is possible or even not:
auto mat1 = matrix<int,2,2>{1, 2, 3, 4};
auto mat2 = matrix<int,2,2>{1, 2, 3, 4, 5}; // Error! Hopefully at compile time
auto mat3 = matrix{1,2,3,4}; // matrix<int,2,2>
Is there a elegant way to express that? With Constructors and initializer_list? Is it possible to check the size of provided arguments?

You could add a constructor taking an arbitrary amount of arguments + a deduction guide to calculate the sizes:
template <class T, std::size_t R, std::size_t C>
struct matrix {
// take anything and forward it:
template <class... Args>
matrix(Args&&... args) : _data{std::forward<Args>(args)...} {
// assert that the sizes are correct:
static_assert(sizeof...(Args) == R * C, "invalid init");
}
std::array<T, R * C> _data;
};
// integer square root
template<std::size_t T, std::size_t N = 0>
constexpr std::size_t isqrt() {
if constexpr ((N+1) * (N+1) <= T) return isqrt<T, N + 1>();
return N;
}
// Deduction guide:
template <class T, class... Args>
matrix(T&&, Args&&...) ->
matrix<T, isqrt<sizeof...(Args) + 1>(), isqrt<sizeof...(Args) + 1>()>;
Demo

Related

How to make a function or constructor that accepts array of any rank

I have a wrapper struct around a vector and a shape, like this:
template <std::size_t N>
struct array_type {
std::array<std::size_t, N> shape;
std::vector<float> data;
};
I would like to be able to construct an array_type from any array float[N], float[N][M], etc.
Currently, I have a function for each rank, e.g.
template <std::size_t N>
array_type<1> _1d(const deel::formal::float_type (&values)[N]) {
return {{N},
std::vector<float_type>(
reinterpret_cast<const float_type*>(values),
reinterpret_cast<const float_type*>(values) + N)};
}
template <std::size_t N>
array_type<2> _2d(const deel::formal::float_type (&values)[N][M]) {
return {{N, M},
std::vector<float_type>(
reinterpret_cast<const float_type*>(values),
reinterpret_cast<const float_type*>(values) + N * M)};
}
I would like to write something like:
template <class Array>
array_type<std::rank_v<Array>> make_array(Array const&);
...but that does not work for initializer list:
auto arr1 = _1d({1, 2, 3}); // Ok
auto arr2 = make_array({1, 2, 3}); // Ko
Is there a way to have this make_array? Or (since I think it's not possible), to have at least something like make_array<1>({1, 2, 3}) where the rank is explicitly specified?
array_type::shape can be generated with std::extent with a bit of template meta programming:
template<typename Array, std::size_t... I>
auto extents_impl(const Array& a, std::index_sequence<I...>)
{
return std::array{std::extent_v<Array, I>...};
}
template<typename Array>
auto extents(const Array& a)
{
return extents_impl(a, std::make_index_sequence<std::rank_v<Array>>());
}
With this, make_array can be written as:
template <class Array, std::enable_if_t<std::is_same_v<std::remove_cv_t<std::remove_all_extents_t<Array>>, float>, int> = 0> // magic incantation :)
array_type<std::rank_v<Array>> make_array(Array const& a)
{
array_type<std::rank_v<Array>> ret {
.shape = extents(a),
.data = std::vector<float>(sizeof a / sizeof(float)),
};
std::memcpy(ret.data.data(), &a, sizeof a);
return ret;
}
I use memcpy to avoid potential pointer type aliasing violations and the technicality of iterating outside the bounds of sub array being UB according to strict interpretation of the standard.
...but that does not work for initializer list:
Add one overload:
template <std::size_t N>
array_type<1> make_array(float const (&a)[N])
{
return make_array<float[N]>(a);
}
Alternatively, you can specify the array type in the call:
make_array<float[2][3]>({{1, 2, 3},{4, 5, 6}});
This requires no overloads.
T (&&)[N] is suggested when you want a function template to accept a braced-init-list and deduce its length, furthermore, you can use T (&&)[M][N], T (&&)[M][N][O] ... to deduce the lengths of each rank (sadly, there is nothing like T (&&)[N]...[Z] to accept array with arbitrary rank).
so you can provide overloading function like this:
// attention that `T (&&)[N][M]` is `Y (&&)[N]` where `Y` is `T[M]`.
template<typename T, size_t N>
using Array1D_t = T[N];
template<typename T, size_t M, size_t N>
using Array2D_t = Array1D_t<Array1D_t<T, N>, M>;
template<typename T, size_t M, size_t N, size_t O>
using Array3D_t = Array1D_t<Array2D_t<T, N, O>, M>;
// ...
// assume the max rank is X.
template<typename T, size_t N>
array_type<1> make_array(Array1D_t<T, N>&& v);
template<typename T, size_t M, size_t N>
array_type<2> make_array(Array2D_t<T, M, N>&& v);
template<typename T, size_t M, size_t N, size_t O>
array_type<3> make_array(Array3D_t<T, M, N, O>&& v);
// ...
and then, it will be okay for make_array({1, 2, 3}), make_array({{1, 2, 3}, {4, 5, 6}}) ..., except the rank is larger than X.
Is there a way to have this make_array?
I don't think so (but, to be honest, I'm not able to demonstrate it's impossible).
Or (since I think it's not possible), to have at least something like make_array<1>({1, 2, 3}) where the rank is explicitly specified?
I don't see a way also in this case.
But... if you accept that the sizes are explicitly specified... you can write a recursive custom type traits to construct the final type
// declararion and ground case
template <typename T, std::size_t ...>
struct get_ranked_array
{ using type = T; };
// recursive case
template <typename T, std::size_t Dim0, std::size_t ... Dims>
struct get_ranked_array<T, Dim0, Dims...>
: public get_ranked_array<T[Dim0], Dims...>
{ };
template <typename T, std::size_t ... Dims>
using get_ranked_array_t = typename get_ranked_array<T, Dims...>::type;
and the make function simply become
template <std::size_t ... Dims>
auto make_ranked_array (get_ranked_array_t<float, Dims...> const & values)
-> array_type<sizeof...(Dims)>
{
return {{Dims...},
std::vector<float>(
reinterpret_cast<float const *>(values),
reinterpret_cast<float const *>(values) + (Dims * ...))};
}
You can use it as follows
auto arr1 = make_ranked_array<3u>({1.0f, 2.0f, 3.0f});
auto arr2 = make_ranked_array<3u, 2u>({ {1.0f, 2.0f, 3.0f},
{4.0f, 5.0f, 6.0f} });
If you want, you can maintain the deduction of the last size, but I don't know if it's a good idea.
The following is a full compiling C++17 example
#include <array>
#include <vector>
template <std::size_t N>
struct array_type
{
std::array<std::size_t, N> shape;
std::vector<float> data;
};
// declararion and ground case
template <typename T, std::size_t ...>
struct get_ranked_array
{ using type = T; };
// recursive case
template <typename T, std::size_t Dim0, std::size_t ... Dims>
struct get_ranked_array<T, Dim0, Dims...>
: public get_ranked_array<T[Dim0], Dims...>
{ };
template <typename T, std::size_t ... Dims>
using get_ranked_array_t = typename get_ranked_array<T, Dims...>::type;
template <std::size_t ... Dims>
auto make_ranked_array (get_ranked_array_t<float, Dims...> const & values)
-> array_type<sizeof...(Dims)>
{
return {{Dims...},
std::vector<float>(
reinterpret_cast<float const *>(values),
reinterpret_cast<float const *>(values) + (Dims * ...))};
}
int main ()
{
auto arr1 = make_ranked_array<3u>({1.0f, 2.0f, 3.0f});
auto arr2 = make_ranked_array<3u, 2u>({ {1.0f, 2.0f, 3.0f},
{4.0f, 5.0f, 6.0f} });
static_assert ( std::is_same_v<decltype(arr1), array_type<1>> );
static_assert ( std::is_same_v<decltype(arr2), array_type<2>> );
}

Expand two parameter packs

Consider following piece of code:
static constexpr size_t Num {2};
struct S {
std::array<size_t, Num> get () { return {1, 2}; }
};
struct S1 : S {};
struct S2 : S {};
struct M {
template <typename T>
typename std::enable_if<std::is_same<T, S1>::value, S1>::type get () const {
return S1 {};
}
template <typename T>
typename std::enable_if<std::is_same<T, S2>::value, S2>::type get () const {
return S2 {};
}
};
I want to have a function which merges two or more std::arrays making one std::array.
So far I ended with something like this:
template <typename Mode, typename... Rs, size_t... Ns>
std::array<size_t, sizeof... (Rs)*Num> get_array (const Mode& mode, Sequence::Sequence<Ns...>) {
return {std::get<Ns> (mode.template get<Rs...> ().get ())...};
}
I want to have that the following code
M m;
auto x = get_array<M, S1, S2> (m, Sequence::Make<2> {});
produces std::array<size_t, 4> filled with {1, 2, 1, 2}.
Where Sequence::Sequence and Sequence::Make are described here.
I know that placing ... of Rs is incorrect in this context (If sizeof... (Rs) is 1 then it is fine, std::array<size_t, 2> with {1, 2} is returned) but I have no idea where to put it to make expansion which looks like this:
std::get<0> (mode.template get<Rs[0]> ().get ()),
std::get<1> (mode.template get<Rs[0]> ().get ()),
std::get<0> (mode.template get<Rs[1]> ().get ()),
std::get<1> (mode.template get<Rs[1]> ().get ());
Of course Rs[0] I mean first type from parameter pack.
Is it even possible?
Assuming that we're using Xeo's index sequence implementation, we can do something like this:
First create a function for concatenating two arrays. It receives the arrays, plus an index sequence for each one (detail::seq is the index_sequence type)
template<class T, size_t N, size_t M, size_t... I, size_t... J>
std::array<T, N + M> concat(const std::array<T, N>& arr1, const std::array<T, M>& arr2, detail::seq<I...>, detail::seq<J...>)
{
return {arr1[I]..., arr2[J]...};
}
Next, call this function from your get_array function, except we're going to double the seq that we received from the call in main:
template<class MODE, class... T, size_t... I>
auto get_array(MODE m, detail::seq<I...>) ->decltype(concat(m.template get<T>().get()..., detail::seq<I...>{}, detail::seq<I...>{})){
return concat(m.template get<T>().get()..., detail::seq<I...>{}, detail::seq<I...>{});
}
The call in main looks just like it did in your code:
M m;
auto x = get_array<M, S1, S2>(m, detail::gen_seq<2>{});
Where detail::gen_seq is the implementation of make_index_sequence that Xeo had.
Live Demo
Note that I replaced unsigned with size_t in Xeo's index sequence impl.
In C++14 we don't need to implement seq or gen_seq, and we also wouldn't need a trailing -> decltype() after our function.
In C++17 it would be even easier to generalize our concatenation for an arbitrary number of arrays, using fold expressions.
Yes, this can be done, with the standard index_sequence tricks:
template <class T, std::size_t N1, std::size_t N2, std::size_t ... Is, std::size_t ... Js>
std::array<T, N1 + N2> merge_impl(const std::array<T, N1>& a1,
const std::array<T, N2>& a2,
std::index_sequence<Is...>,
std::index_sequence<Js...>) {
return {a1[Is]..., a2[Js]...};
}
template <class T, std::size_t N1, std::size_t N2>
std::array<T, N1 + N2> merge(const std::array<T, N1>& a1, const std::array<T, N2>& a2) {
return merge_impl(a1, a2,
std::make_index_sequence<N1>{},
std::make_index_sequence<N2>{});
}
index_sequence is only in the 14 standard, but can be easily implemented in 11; there are many resources (including on SO) that describe how to do so (edit: it's basically equivalent to your Sequence stuff, may as well get used to the standard names for them). Live example: http://coliru.stacked-crooked.com/a/54dce4a695357359.
To start with, this is basically asking to concatenate an arbitrary number of arrays. Which is very similar to concatenate an arbitrary number of tuples, for which there is a standard library function, even in C++11: std::tuple_cat(). That gets us almost there:
template <class... Ts, class M>
auto get_array(M m) -> decltype(std::tuple_cat(m.template get<Ts>()...)) {
return std::tuple_cat(m.template get<Ts>()...);
}
Note that I flipped the template parameters, so this is just get_array<T1, T2>(m) instead of having to write get_array<M, T1, T2>(m).
Now the question is, how do we write array_cat? We'll just use tuple_cat and convert the resulting tuple to an array. Assume an implementation of index_sequence is available (which is something you'll want in your collection anyway):
template <class T, class... Ts, size_t... Is>
std::array<T, sizeof...(Ts)+1> to_array_impl(std::tuple<T, Ts...>&& tup,
std::index_sequence<Is...> ) {
return {{std::get<Is>(std::move(tup))...}};
}
template <class T, class... Ts>
std::array<T, sizeof...(Ts)+1> to_array(std::tuple<T, Ts...>&& tup) {
return to_array_impl(std::move(tup), std::index_sequence_for<T, Ts...>());
}
template <class... Tuples>
auto array_cat(Tuples&&... tuples) -> decltype(to_array(std::tuple_cat(std::forward<Tuples>(tuples)...))) {
return to_array(std::tuple_cat(std::forward<Tuples>(tuples)...));
}
And that gives you:
template <class... Ts, class M>
auto get_array(M m) -> decltype(array_cat(m.template get<Ts>()...)) {
return array_cat(m.template get<Ts>()...);
}
which handles arbitrarily many types.
So here's for an arbitrary number of same-type arrays. We are basically implementing a highly restrictive version of tuple_cat, made substantially easier because the number of elements in the arrays is the same. I make use of a couple C++14 and 17 library features that are all readily implementable in C++11.
template<class, size_t> struct div_sequence;
template<size_t...Is, size_t Divisor>
struct div_sequence<std::index_sequence<Is...>, Divisor>
{
using quot = std::index_sequence<Is / Divisor...>;
using rem = std::index_sequence<Is % Divisor...>;
};
template<class T, size_t...Ns, size_t...Is, class ToA>
std::array<T, sizeof...(Ns)> array_cat_impl(std::index_sequence<Ns...>,
std::index_sequence<Is...>,
ToA&& t)
{
// NB: get gives you perfect forwarding; [] doesn't.
return {std::get<Is>(std::get<Ns>(std::forward<ToA>(t)))... };
}
template<class Array, class... Arrays,
class VT = typename std::decay_t<Array>::value_type,
size_t S = std::tuple_size<std::decay_t<Array>>::value,
size_t N = S * (1 + sizeof...(Arrays))>
std::array<VT, N> array_cat(Array&& a1, Arrays&&... as)
{
static_assert(std::conjunction_v<std::is_same<std::decay_t<Array>,
std::decay_t<Arrays>>...
>, "Array type mismatch");
using ind_seq = typename div_sequence<std::make_index_sequence<N>, S>::rem;
using arr_seq = typename div_sequence<std::make_index_sequence<N>, S>::quot;
return array_cat_impl<VT>(arr_seq(), ind_seq(),
std::forward_as_tuple(std::forward<Array>(a1),
std::forward<Arrays>(as)...)
);
}
We can also reuse the tuple_cat machinery, as in #Barry's answer. To sidestep potential QoI issues, avoid depending on extensions and also extra moves, we don't want to tuple_cat std::arrays directly. Instead, we transform the array into a tuple of references first.
template<class TupleLike, size_t... Is>
auto as_tuple_ref(TupleLike&& t, std::index_sequence<Is...>)
-> decltype(std::forward_as_tuple(std::get<Is>(std::forward<TupleLike>(t))...))
{
return std::forward_as_tuple(std::get<Is>(std::forward<TupleLike>(t))...);
}
template<class TupleLike,
size_t S = std::tuple_size<std::decay_t<TupleLike>>::value >
auto as_tuple_ref(TupleLike&& t)
-> decltype(as_tuple_ref(std::forward<TupleLike>(t), std::make_index_sequence<S>()))
{
return as_tuple_ref(std::forward<TupleLike>(t), std::make_index_sequence<S>());
}
We can then transform the tuple_cat'd references back into an array:
template <class R1, class...Rs, size_t... Is>
std::array<std::decay_t<R1>, sizeof...(Is)>
to_array(std::tuple<R1, Rs...> t, std::index_sequence<Is...>)
{
return { std::get<Is>(std::move(t))... };
}
template <class R1, class...Rs>
std::array<std::decay_t<R1>, sizeof...(Rs) + 1> to_array(std::tuple<R1, Rs...> t)
{
static_assert(std::conjunction_v<std::is_same<std::decay_t<R1>, std::decay_t<Rs>>...>,
"Array element type mismatch");
return to_array(t, std::make_index_sequence<sizeof...(Rs) + 1>());
}
Finally, array_cat itself is just
template <class... Arrays>
auto array_cat(Arrays&&... arrays)
-> decltype(to_array(std::tuple_cat(as_tuple_ref(std::forward<Arrays>(arrays))...)))
{
return to_array(std::tuple_cat(as_tuple_ref(std::forward<Arrays>(arrays))...));
}
Any decent optimizer should have little difficulty optimizing the intermediate tuples of references away.

Getting the size of each type of the parameter pack?

After a long day of unsuccessful searching in the web for a practical solution to solve my problem, I decided to post my issue here, to clarify my goal I provided this simple code:
template<typename... types>
std::vector<SIZE_T> GetTypesSize()
{
std::vector<SIZE_T> typesSizeContainer;
typesSizeContainer.reserve(sizeof... (types));
/*
* What I want here is a mechanism to loop throw
* each element of the parameter pack to get its size
* then push it into typesSizeContainer.
* Something similar to :
*
* for(auto& element : types...) {
* typesSizeContainer.push(sizeof(element));
* }
*
*/
return std::move(typesSizeContainer);
}
When I call this function template in this code:
// platform x86
std::vector<SIZE_T> tempVactor;
tempVactor = GetTypesSize<char, short, int>();
The elements of the tempVactor should be { 1, 2, 4 }.
Any suggestion or solution is considerable.
I would recommend using std::array for that:
template<typename... Types>
constexpr auto GetTypesSize() {
return std::array<std::size_t, sizeof...(Types)>{sizeof(Types)...};
}
You can just initialize the vector with the unpacked sizes:
template<typename... types>
std::vector<size_t> GetTypesSize()
{
return { sizeof(types)... };
}
demo
There is another possible solution which illustrates how to solve the problem using SFINAE:
template<size_t N>
typename std::enable_if<N == 0>::type get(std::vector<std::size_t>& sizes) {}
template<size_t N, typename T, typename... Args>
typename std::enable_if<N != 0>::type get(std::vector<std::size_t>& sizes) {
sizes.push_back(sizeof(T));
get<N - 1, Args...>(sizes);
}
template<typename... Args>
const std::vector<std::size_t> get() {
std::vector<std::size_t> sizes;
get<sizeof...(Args), Args...>(sizes);
return sizes;
}

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.

Spliting a std::array into a tuple of smaller sized std::array

I'm trying to split a std::array<T, N> into a tuple of smaller arrays, like std::tuple<std::array<T, N1>, std::array<T, N2>, ...> where N1 + N2 + ... = N.
namespace detail {
// Summation of the given values
template <class T>
constexpr T sum(const T& x) { return x; }
template <class T, class ...Args>
constexpr auto sum(const T& x, Args&&... args)
{ return x + sum(std::forward<Args>(args)...); }
}
template <class T, std::size_t... Ns>
constexpr
std::tuple<std::array<T, Ns>...>
f(const std::array<T, detail::sum(Ns...)>& x)
{
// How do I implement this function?
}
int main()
{
constexpr std::array<Foo, 5> arr = { ... };
constexpr auto t = f<Foo, 2,3>(arr);
}
Actually I already implemented f but it's based on a loop which simply creates an empty array and copies the elements of the given array, but it doesn't work if T is not default_constructible.
I tried to utilize std::integer_sequence and std::make_index_sequence, but I think i'm totally lost with no clue.
Can anyone help me implement the function please?
Write
template<class T,size_t...Is,size_t N>
std::array<T,sizeof...(Is)>
extract(std::array<T,N>const&,std::index_sequence<Is...>){
return {{arr[Is]...}};
}
now we just need to turn {1,2,3} into {{0},{1,2},{3,4,5}} roughly, with everything being C++ index sequences (so syntax).
Map {3,4,0} to {0,1,2} -- a count of indexes to subarrays. Then map {3,4,0} x 1 to {3,4,5,6} and similar for the others. That gives us the indexes inside the subarrays, which we feed to extract and bob is your uncle.
template<size_t n, size_t...counts>
constexpr auto
foo( std::index_sequence<counts...> )
-> offset_seq<
sum_n<n>(counts...),
std::make_index_sequence<get_n<n,counts...> >
>{ return {}; }
with various helpers to be written is the {3,4,0} x 1 to {3,4,5,6} portion, for example.