I have a custom implementation for arrays with arbitrary upper and lower bounds. Now I want to be able to freely convert between arrays of the same type, as long as they have the same length. In code it looks like this:
template<typename T, int L, int H>
class Array{
public:
// stuff ...
operator Array<T, int lb, int hb>& () {
auto converted = Array<T, lb, hb>;
converted.actualArray = actualArray;
converted.offset = lb;
return *converted;
}
private:
T actualArray[H - L + 1];
int offset = 0 - L;
}
As you can see, the class needs to convert to itself. As you can probably also see, I am quite the noob at C++, as the error I'm given appears to be a syntax one:
wrong number of template arguments (2, should be 3)
operator Array<T, int lb, int hb>& () {
^
'<expression error>' does not name a type
operator Array<T, int lb, int hb>& () {
^
What am I doing wrong, that my operator's return type is not recognized? I really hope it's not a simple typo, that would be stupid.
You need a second set of template parameters for the operator itself, so it can be called with different template values than the main class uses.
Even if you could get the template parameters right, it still wouldn't work since you can't assign a raw array to another raw array. You need to copy the elements instead, such as with std:copy() or std::copy_n().
Try something more like this:
#include <algorithm>
template<typename T, size_t L, size_t H>
class Array
{
public:
static_assert(H >= L);
static const size_t Low = L;
static const size_t High = H;
static const size_t Length = (H - L + 1);
// stuff ...
template<size_t Lb, size_t Hb>
operator Array<T, Lb, Hb>() const
{
static_assert(Length == Array<T, Lb, Hb>::Length);
Array<T, Lb, Hb> converted;
std::copy_n(actualArray, Length, converted.actualArray);
return converted;
}
// just to show that you don't need an offset member...
T& operator[](size_t idx)
{
return actualArray[idx - L];
}
T operator[](size_t idx) const
{
return actualArray[idx - L];
}
template<typename, size_t, size_t>
friend class Array;
private:
T actualArray[Length];
};
Live Demo
Alternative, you could define a copy constructor that accepts multiple Array types of the same array size, and then you don't need the conversion operator anymore:
#include <algorithm>
template<typename T, size_t L, size_t H>
class Array
{
public:
static_assert(H >= L);
static const size_t Low = L;
static const size_t High = H;
static const size_t Length = (H - L + 1);
// stuff ...
Array() = default;
template<size_t Lb, size_t Hb>
Array(const Array<T, Lb, Hb> &src)
{
static_assert(Length == Array<T, Lb, Hb>::Length);
std::copy_n(src.actualArray, Length, actualArray);
}
// just to show that you don't need an offset member...
T& operator[](size_t idx)
{
return actualArray[idx - L];
}
T operator[](size_t idx) const
{
return actualArray[idx - L];
}
template<typename, size_t, size_t>
friend class Array;
private:
T actualArray[Length];
};
Live Demo
Related
I'm still rather new to TMP so forgive me if this is a poorly worded question.
I'm trying to make a very generic mathematical Vector class to store any number of components, but defaults to 3 and using float as it's base representation. So if you default construct one of these vectors it will hold (0.0f,0.0f,0.0f)
The values themselves are stored in a std::array and I would like to create accessor function for ease of use. I currently have this:
std::array<Type,SIZE> e;
Type x() const {return e.at(0);};
Type y() const {return e.at(1);};
Type z() const {return e.at(2);};
What I am trying to do now is also have one for the 4th component, w but only enable it if the size of this array is >= 4. so something like this:
template<class Type, std::enable_if<.......>>
Type w() const {return e.at(3);};
This is just a vague idea of what I think it should look like. I'm aware concept exists, but I'm also struggling to write one for this situation.
With concepts you can simply do
Type w() const requires (SIZE >= 4) {return e.at(3);};
Prior to C++20 concepts, you can do this:
template<typename Type = float, size_t SIZE = 3>
class Vector {
std::array<Type, SIZE> e;
public:
template<typename Dummy = void, std::enable_if_t<SIZE >= 3, Dummy>* = nullptr>
Type x() const { return e[0]; };
template<typename Dummy = void, std::enable_if_t<SIZE >= 3, Dummy>* = nullptr>
Type y() const { return e[1]; };
template<typename Dummy = void, std::enable_if_t<SIZE >= 3, Dummy>* = nullptr>
Type z() const { return e[2]; };
template<typename Dummy = void, std::enable_if_t<SIZE >= 4, Dummy>* = nullptr>
Type w() const { return e[3]; };
};
Vector v1;
v1.x();
v1.y();
v1.z();
//v1.w(); // ERROR
Vector<int, 4> v2;
v2.x();
v2.y();
v2.z();
v2.w(); // OK
Online Demo
Alternatively, you can also do this, like #Jarod42 suggested:
template<typename Type = float, size_t SIZE = 3>
class Vector {
std::array<Type, SIZE> e;
public:
template<size_t N = SIZE, std::enable_if_t<N >= 3>* = nullptr>
Type x() const { return e[0]; };
template<size_t N = SIZE, std::enable_if_t<N >= 3>* = nullptr>
Type y() const { return e[1]; };
template<size_t N = SIZE, std::enable_if_t<N >= 3>* = nullptr>
Type z() const { return e[2]; };
template<size_t N = SIZE, std::enable_if_t<N >= 4>* = nullptr>
Type w() const { return e[3]; };
};
Online Demo
However, that would allow the user to explicitly make w() accessible to read from an invalid index, eg:
Vector v1; // only has indexes 0..2
v1.w<5>(); // NO ERROR, but accesses index 3
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.
I often work with multi-dimensional arrays, and I am not a big fan of std::vector, since it is not possible to instantiate a std::vector or a std::vector of std::vector's using a reference without copying the underlying data.
For one-dimensional arrays, I use the following
template<typename T>
using deleted_aligned_array = std::unique_ptr<T[], std::function<void(T*)> >;
template<typename T>
deleted_aligned_array<T> deleted_aligned_array_create(size_t n) {
return deleted_aligned_array<T>((T*)_mm_malloc(n*sizeof(T),16), [](T* f)->void { _mm_free(f);});
}
This is very convenient and allows me to instantiate a dynamically sized array, which also works for a size of zero. Further, I can use std::forward to pass on the data without copying.
For a two-dimensional array, I would like to do something like
template<typename T>
using deleted_aligned_array2 = std::unique_ptr<T*,std::function<void(T**)>>;
template<typename T>
deleted_aligned_array2<T> deleted_aligned_array_create(size_t m, size_t n) {
auto arr = deleted_aligned_array2(new T*[m](), [&](T** x) {
if (malloc_usable_size(x) > 0) {
_mm_free(&(x[0][0]));
}
delete[] x;});
if (m*n > 0) {
arr.get()[0] = (T*) _mm_malloc(m*n*sizeof(T),16);
// Row pointers
for (size_t iRow = 1; iRow < m; iRow++) {
(m_data.get())[iRow] = &(m_data.get()[0][iRow*n]);
}
}
return arr;
}
It works for zero-size arrays, but I get an error from valgrind for obvious reasons, invalid read of size 8.
Is it possible to solve this in an elegant way, without creating an entire class keeping a std::unique_ptr member, where I implement move-constructor, move-assignment etc. Ultimately, I would like to generalize this to be used for any dimension
template<typename T, size_t Dim>
deleted_aligned_array<T,D> deleted_aligned_array_create(...);
The returned array should be a unique pointer with row pointer recursively initialized and it should support zero-size arrays, e.g.
auto arr = deleted_aligned_array_create<float,3>(4,5,10);
should return a 3-dimensional array with row and column pointers.
Issues:
1) Avoid reading invalid data in a simple way.
2) Use a template parameter D for generating the types: T*, T** and simply passing on D to code recursively generating row pointers (this I already have).
3) Preferably in a portable way. malloc_usable_size is a GNU extension and calling it on x results in an invalid read, when the size is 0.
Thanks in advance
I sort of found a solution but it is not very elegant. If you have a more elegant solution, please post your answer. The solution here is pretty ugly, once we get to higher dimensions.
template <class T, size_t D>
class deleted_aligned_multi_array {
};
template <class T>
class deleted_aligned_multi_array<T,1> : public std::unique_ptr<T[], std::function<void(T*)> > {
deleted_aligned_multi_array(size_t n) :
std::unique_ptr<T[], std::function<void(T*)> >((T*)_mm_malloc(n*sizeof(T),16),
[](T* f)->void { _mm_free(f);}) {}
};
template <class T>
class deleted_aligned_multi_array<T,2> {
public:
typedef T** pointer;
typedef std::unique_ptr<T*, std::function<void(T**)>> deleted_unique_array;
deleted_aligned_multi_array() : m(0), n(0), data() {}
deleted_aligned_multi_array(size_t m, size_t n) : m(m), n(n) {
if (m*n > 0) {
data = deleted_unique_array(new T*[m](),
[&](T** x) {
if (sps::msize(x) > 0) {
_mm_free(&(x[0][0]));
}
delete[] x;});
data.get()[0] = (T*) _mm_malloc(m*n*sizeof(T),16);
for (size_t iRow = 1; iRow < m; iRow++) {
(data.get())[iRow] = &(data.get()[0][iRow*n]);
}
}
else {
data.reset();
}
}
deleted_aligned_multi_array(deleted_aligned_multi_array&& other) : m(other.m), n(other.n),
data(std::move(other.data)) {}
deleted_aligned_multi_array& operator=( deleted_aligned_multi_array&& other ) {
if (this != &other) {
data = std::move( other.data );
m = other.m;
m = other.n;
}
return *this;
}
T& operator()(size_t i, size_t j) {
return this->data.get()[0][i*n + j];
}
T* operator[](size_t m) {
return &(data.get()[m][0]);
}
const T* operator[](size_t m) const {
return data.get()[m];
}
pointer get() const {
return data.get();
}
void reset(pointer __p = pointer()) {
data.reset(__p);
}
template<typename _Up>
void reset(_Up) = delete;
private:
deleted_aligned_multi_array(const deleted_aligned_multi_array& other) = delete;
deleted_aligned_multi_array& operator=( const deleted_aligned_multi_array& a ) = delete;
public:
size_t m; ///<Number of rows
size_t n; ///<Number of columns
deleted_unique_array data; ///<Data
};
A utility function for accessing a sub array, can now easily be made
template <class T>
std::unique_ptr<T*, std::function<void(T*)> sub_array(size_t m, size_t n, size_t i, size_t j) {
// Establish row pointers with reference i and j and dimension mxn.
}
I am using boost pool as a static memory provider,
void func()
{
std::vector<int, boost::pool_allocator<int> > v;
for (int i = 0; i < 10000; ++i)
v.push_back(13);
}
In above code, how we can fix the size of pool, i mean as we know boost::pool provide as a static memory allocator, but i am not able to fix the size of this pool, its keep growing, there should be way to restrict its size.
for example i want a pool of 200 chunks only so i can take 200 chunks after that it should though NULL
please let me now how to do this
I don't think boost pool provides what you want. Actually there are 4 other template parameters for boost::pool_allocator except the type of object:
UserAllocator: Defines the method that the underlying Pool will use to allocate memory from the system(default = boost::default_user_allocator_new_delete).
Mutex: Allows the user to determine the type of synchronization to be used on the underlying singleton_pool(default = boost::details::pool::default_mutex).
NextSize: The value of this parameter is passed to the underlying Pool when it is created and specifies the number of chunks to allocate in the first allocation request (default = 32).
MaxSize: The value of this parameter is passed to the underlying Pool when it is created and specifies the maximum number of chunks to allocate in any single allocation request (default = 0).
You may think MaxSize is exactly what you want, but unfortunately it's not.
boost::pool_allocator uses a underlying boost::singleton_pool which is based on an boost::pool, the MaxSize will eventually pass to the data member of boost::pool<>: max_size, so what role does the max_size play in the boost::pool? let's have a look at boost::pool::malloc():
void * malloc BOOST_PREVENT_MACRO_SUBSTITUTION()
{ //! Allocates a chunk of memory. Searches in the list of memory blocks
//! for a block that has a free chunk, and returns that free chunk if found.
//! Otherwise, creates a new memory block, adds its free list to pool's free list,
//! \returns a free chunk from that block.
//! If a new memory block cannot be allocated, returns 0. Amortized O(1).
// Look for a non-empty storage
if (!store().empty())
return (store().malloc)();
return malloc_need_resize();
}
Obviously, boost::pool immediately allocates a new memory block if no free chunk available in the memory block. Let's continue to dig into the malloc_need_resize():
template <typename UserAllocator>
void * pool<UserAllocator>::malloc_need_resize()
{ //! No memory in any of our storages; make a new storage,
//! Allocates chunk in newly malloc aftert resize.
//! \returns pointer to chunk.
size_type partition_size = alloc_size();
size_type POD_size = static_cast<size_type>(next_size * partition_size +
math::static_lcm<sizeof(size_type), sizeof(void *)>::value + sizeof(size_type));
char * ptr = (UserAllocator::malloc)(POD_size);
if (ptr == 0)
{
if(next_size > 4)
{
next_size >>= 1;
partition_size = alloc_size();
POD_size = static_cast<size_type>(next_size * partition_size +
math::static_lcm<sizeof(size_type), sizeof(void *)>::value + sizeof(size_type));
ptr = (UserAllocator::malloc)(POD_size);
}
if(ptr == 0)
return 0;
}
const details::PODptr<size_type> node(ptr, POD_size);
BOOST_USING_STD_MIN();
if(!max_size)
next_size <<= 1;
else if( next_size*partition_size/requested_size < max_size)
next_size = min BOOST_PREVENT_MACRO_SUBSTITUTION(next_size << 1, max_size*requested_size/ partition_size);
// initialize it,
store().add_block(node.begin(), node.element_size(), partition_size);
// insert it into the list,
node.next(list);
list = node;
// and return a chunk from it.
return (store().malloc)();
}
As we can see from the source code, max_size is just related to the number of chunks to request from the system next time, we can only slow down the speed of increasing via this parameter.
But notice that we can defines the method that the underlying pool will use to allocate memory from the system, if we restrict the size of memory allocated from system, the pool's size wouldn't keep growing. In this way, boost::pool seems superfluous, you can pass the custom allocator to STL container directly. Here is a example of custom allocator(based on this link) which allocates memory from stack up to a given size:
#include <cassert>
#include <iostream>
#include <vector>
#include <new>
template <std::size_t N>
class arena
{
static const std::size_t alignment = 8;
alignas(alignment) char buf_[N];
char* ptr_;
bool
pointer_in_buffer(char* p) noexcept
{ return buf_ <= p && p <= buf_ + N; }
public:
arena() noexcept : ptr_(buf_) {}
~arena() { ptr_ = nullptr; }
arena(const arena&) = delete;
arena& operator=(const arena&) = delete;
char* allocate(std::size_t n);
void deallocate(char* p, std::size_t n) noexcept;
static constexpr std::size_t size() { return N; }
std::size_t used() const { return static_cast<std::size_t>(ptr_ - buf_); }
void reset() { ptr_ = buf_; }
};
template <std::size_t N>
char*
arena<N>::allocate(std::size_t n)
{
assert(pointer_in_buffer(ptr_) && "short_alloc has outlived arena");
if (buf_ + N - ptr_ >= n)
{
char* r = ptr_;
ptr_ += n;
return r;
}
std::cout << "no memory available!\n";
return NULL;
}
template <std::size_t N>
void
arena<N>::deallocate(char* p, std::size_t n) noexcept
{
assert(pointer_in_buffer(ptr_) && "short_alloc has outlived arena");
if (pointer_in_buffer(p))
{
if (p + n == ptr_)
ptr_ = p;
}
}
template <class T, std::size_t N>
class short_alloc
{
arena<N>& a_;
public:
typedef T value_type;
public:
template <class _Up> struct rebind { typedef short_alloc<_Up, N> other; };
short_alloc(arena<N>& a) noexcept : a_(a) {}
template <class U>
short_alloc(const short_alloc<U, N>& a) noexcept
: a_(a.a_) {}
short_alloc(const short_alloc&) = default;
short_alloc& operator=(const short_alloc&) = delete;
T* allocate(std::size_t n)
{
return reinterpret_cast<T*>(a_.allocate(n*sizeof(T)));
}
void deallocate(T* p, std::size_t n) noexcept
{
a_.deallocate(reinterpret_cast<char*>(p), n*sizeof(T));
}
template <class T1, std::size_t N1, class U, std::size_t M>
friend
bool
operator==(const short_alloc<T1, N1>& x, const short_alloc<U, M>& y) noexcept;
template <class U, std::size_t M> friend class short_alloc;
};
template <class T, std::size_t N, class U, std::size_t M>
inline
bool
operator==(const short_alloc<T, N>& x, const short_alloc<U, M>& y) noexcept
{
return N == M && &x.a_ == &y.a_;
}
template <class T, std::size_t N, class U, std::size_t M>
inline
bool
operator!=(const short_alloc<T, N>& x, const short_alloc<U, M>& y) noexcept
{
return !(x == y);
}
int main()
{
const unsigned N = 1024;
typedef short_alloc<int, N> Alloc;
typedef std::vector<int, Alloc> SmallVector;
arena<N> a;
SmallVector v{ Alloc(a) };
for (int i = 0; i < 400; ++i)
{
v.push_back(10);
}
}
I'm trying to get familiar with C++ templates. I need to write a template of function that concatenates 2 arrays:
template<typename T, int Size>
class Array
{
public:
void push(int i, const T& t) { _elem[i] = t; }
private:
T _elem[Size];
};
For example I have 2 arrays:
Array<int,3> a1;
Array<int,4> a2;
I don't know how to write this function, that will return
Array<int,7>.
How should header of this function look like?
You should try it like this:
template<typename T, int A, int B>
Array<T, A+B> concatenate(Array<T, A> first, Array<T, B> second)
{
Array<T, A+B> result;
for (int idx = 0; idx < A; ++idx) {
result.push(idx, first[idx]);
}
for (int idx = 0; idx < B; ++idx) {
result.push(idx+A, second[idx]);
}
return result;
}
You could do it like this, as a free-function outside the class:
template <typename T, int SizeA, int SizeB>
Array<T, SizeA + SizeB> join(const Array<T, SizeA>& first,
const Array<T, SizeB>& second)
{
/* ... */
}
For what it's worth, you should probably use std::size_t from <cstddef> instead of int.