get total number of elements in a multidimensional array using std::extent - c++

How to find total number of elements in a multidimensional array using std::extent. This is my sample code:
#include <iostream>
#include <type_traits>
int main()
{
int32_t int_arr[10][100][1000];
int32_t no_of_elements = 1;
// compiler error in below for loop block
for (int32_t i = 0; i < std::rank<decltype(int_arr)>{}; ++i)
no_of_elements *= std::extent<decltype(int_arr), i>{};
no_of_elements *= std::extent<decltype(int_arr), 0>{};
no_of_elements *= std::extent<decltype(int_arr), 1>{};
no_of_elements *= std::extent<decltype(int_arr), 2>{};
std::cout << no_of_elements << std::endl;
std::cout << sizeof(int_arr) / sizeof(std::remove_all_extents<decltype(int_arr)>::type) << std::endl;
return 0;
}
error: the value of ā€˜iā€™ is not usable in a constant expression 9 | no_of_elements *= std::extent<decltype(int_arr), i>{};

With C++14 std::index_sequence, you can use std::extent with something like:
template <typename T, std::size_t... Is>
constexpr std::size_t count_elements(std::index_sequence<Is...>)
{
return (1 * ... * std::extent<T, Is>{}); // C++17 fold expression
// Alternatives exist for C++11/C++14
}
template <typename T>
constexpr std::size_t count_elements()
{
return count_elements<T>(std::make_index_sequence<std::rank<T>{}>());
}
int main()
{
int32_t int_arr[10][100][1000];
int32_t no_of_elements = count_element<decltype(int_arr)>();
std::cout << no_of_elements << std::endl;
}

Much simpler way is to get the type of array element and then sizeof(array)/sizeof(element) is an answer:
int32_t int_arr[10][100][1000];
using element = std::remove_all_extents<decltype(int_arr)>::type;
no_of_elements = sizeof(int_arr)/sizeof(element);
So use remove_all_extents
in templated version (like in C++14 variable template):
template <typename ArrayType>
constexpr std::size_t no_of_elements_v =
sizeof(ArrayType) / sizeof(remove_all_extents_t<ArrayType>;
If you really insist on using C++11 and std::extent - this is the solution:
template <typename T, std::size_t rank = std::rank<T>{}, std::size_t dim = 0>
struct NumOfElements :
std::integral_constant<std::size_t, std::extent<T, dim>{} * NumOfElements<T, rank, dim + 1>::value>
{};
template <typename T, std::size_t rank>
struct NumOfElements<T, rank, rank> :
std::integral_constant<std::size_t, 1u>
{};
And use NumOfElements<decltype(int_arr)>::value
But much, much simpler is this simple template version w/o any type_traits, if you replace integral_constant with simple static member variable - it should work even in pre-C++11:
template <typename T>
struct NumOfElements : std::integral_constant<std::size_t, 1u>
{};
template <typename T, std::size_t N>
struct NumOfElements<T[N]> : std::integral_constant<std::size_t,
N * NumOfElements<T>::value>
{};

#include <iostream>
#include <type_traits>
#include <typeinfo>
template<class A>
size_t getSize(const A&)
{
typedef typename std::remove_all_extents<A>::type Type;
return sizeof(A) / sizeof(Type);
}
int main()
{
int a1[3][10];
float a2[5][10][10];
double a3[3][10];
std::int32_t int_arr[10][100][1000];
std::cout << "Size of array a1: " << getSize(a1) << std::endl;
std::cout << "Size of array a2: " << getSize(a2) << std::endl;
std::cout << "Size of array a3: " << getSize(a3) << std::endl;
std::cout << "Size of array int_arr: " << getSize(int_arr) << std::endl;
}
Output:
Size of array a1: 30
Size of array a2: 500
Size of array a3: 30
Size of array int_arr: 1000000
One minus of this code is function getSize() is generated for each type of array:
type of a1: int[3][10] (GCC outputs: A3_A10_i)
type of a2: float[5][10][10] (GCC outputs: A5_A10_A10_f)
type of a3: double[3][10] (GCC outputs: A3_A10_d)

I don't think std::extent is the right choice here. I'd simply define a "recursive" template:
template<typename T>
struct nested_array_size
{
static constexpr size_t value = 1;
};
template<typename T, size_t N>
struct nested_array_size<T[N]>
{
static constexpr size_t value = N * nested_array_size<T>::value;
};
std::cout << nested_array_size<double[1][2][3][4]>::value << std::endl;
std::cout << nested_array_size<int[5]>::value << std::endl;
std::cout << nested_array_size<int[5][6]>::value << std::endl;

Related

get size of std::array and span generically at compile time

I searched a fair amount of time in the standard library but just can't find the name of the template function calculating the element count of a std::array type or std::span type or c-array type. What I mean is something similar to get_element_count in the example code below.
I am not looking for a solution, get_element_count works fine. I just believed there exists a standard function already.
The function must take a type as a parameter like std::tuple_size for e.g. but needs to be more generic.
So the Question is: What is the name of that function in the standard library? I am happy with the answer from #user17732522 which is that I cannot find it because it does not exist.
#include <iostream>
#include <array>
#include <span>
#include <type_traits>
template<typename>
inline constexpr size_t get_element_count = 0;
template<typename T, size_t N>
inline constexpr size_t get_element_count<std::array<T, N>> = N;
template<typename T, size_t N>
inline constexpr size_t get_element_count<std::span<T, N>> = N;
template<typename T, size_t N>
inline constexpr size_t get_element_count<T[N]> = N;
int main ()
{
std::array<char, 7> a = {'0','1','2','3','4','5','6'};
std::span b = a;
std::span<char, 7> c = a;
char d[7] = {'0','1','2','3','4','5','6'};
long e[7] = { 0L, 1L, 2L, 3L, 4L, 5L, 6L};
std::cout << get_element_count<decltype(a)> << std::endl;
std::cout << get_element_count<decltype(b)> << std::endl;
std::cout << get_element_count<decltype(c)> << std::endl;
std::cout << get_element_count<decltype(d)> << std::endl;
std::cout << get_element_count<decltype(e)> << std::endl;
}
And because some asked for an example where such a compile time function is needed and not only used:
template <typename T, size_t N>
struct S {T t; size_t n = N;};
using A = std::array<int, 5>;
using B = std::span<int, 5>;
template <typename T>
using C = S<typename T::value_type, 2 * get_element_count<T>>;
using D_double = C<A>;
using E_double = C<B>;

Defining a static constexpr member by call to constexpr function

My Problem is the following. I want to sort a list of types based on a list of constexpr values. The problem can be boiled down to this function:
template <typename U, typename V>
auto min(U,V) -> std::conditional_t<U::value < V::value, U, V>
{ return {}; }
whereas value must be some static constexpr member of each type, respecively.
The following snippet demonstrates the usage:
// (I)
// This must even be declared outside of a function body due to the statics :(
struct X { static constexpr double value = 2.; };
struct Y { static constexpr double value = 1.; };
int main()
{
X x;
Y y;
auto z = min(x,y);
std::cout << typeid(z).name() << " : " << z.value << std::endl;
}
My goal is to provide the value as I call the function. The closest thing I got to this goal is
the following
template <double (*F)()>
struct Value { static constexpr double value = F(); };
which can be called like this using lambdas:
// (II)
auto w = min(Value<[]{ return 3.14; }>{}, Value<[]{ return 2.71; }>{});
std::cout << typeid(w).name() << " : " << w.value << std::endl;
The actual type to be sorted can be an additional parameter.
The problem is that the above is not valid C++ according to the standard. However, the latest clang does compile
this gracefully.
Now, my question is: Is there another standard compliant way to achieve the above (listing (II)), that is, defining a function that
computes a type based on constexor objects provided inplace (in some way) as the function argument?
P.S.: I'm aware of the solution using std::integral_constant. This, however, is limited to integral types only. I'm interested in a solution that works for all constexpr objects, in particular floating point types, and strings.
Edit:
To deal with floating point values as well as integral types scenarios you could make use of user defined literal template e.g.:
#include <type_traits>
#include <utility>
#include <typeinfo>
#include <iostream>
template <class FloatingPointType, class... Cs>
constexpr FloatingPointType char_list_to_(Cs... cs) {
char arr[] = {cs...};
FloatingPointType lhs = 0;
bool contains_dot = false;
for (std::size_t i = 0; i < sizeof...(Cs) && !(contains_dot |= (arr[i] == '.')); i++) {
lhs *= 10;
lhs += arr[i] - '0';
}
FloatingPointType rhs = 0;
for (int i = sizeof...(Cs) - 1; i > 0 && arr[i] != '.'; i--) {
rhs /= 10;
rhs += arr[i] - '0';
}
rhs /= 10;
return (contains_dot)?lhs+rhs:lhs;
}
template <class FloatingPointType, char... Cs>
struct FloatingPointValue {
static constexpr FloatingPointType value = char_list_to_<FloatingPointType>(Cs...);
constexpr operator FloatingPointType() {
return value;
}
};
template <class FloatingPointType, char... Cs>
constexpr FloatingPointType FloatingPointValue<FloatingPointType, Cs...>::value;
template <char... Cs>
FloatingPointValue<double, Cs...> operator""_fv() {
return {};
}
template <typename U, typename V>
auto min(U,V) -> std::conditional_t<(U{}<V{}), U, V>
{ return {}; }
int main() {
auto w = min(3.14_fv, 2.71_fv);
std::cout << typeid(w).name() << " : " << w.value << std::endl;
}
Output:
18FloatingPointValueIdJLc50ELc46ELc55ELc49EEE : 2.71
Output of c++filt -t 18FloatingPointValueIdJLc50ELc46ELc55ELc49EEE:
FloatingPointValue<double, (char)50, (char)46, (char)55, (char)49>
[live demo]
But if you wish to apply the same to string literal there is currently a lack of support of the feature caused by a c++ standard. There is however a gnu extension supported by clang and gcc if you are capable to accept less portable option:
#include <type_traits>
#include <utility>
#include <typeinfo>
#include <iostream>
template <class CharT, CharT... Cs>
struct Value {
static constexpr std::size_t size = sizeof...(Cs);
static constexpr CharT const value[sizeof...(Cs) + 1] = {Cs..., '\0'};
template <class RHS>
constexpr bool operator<(RHS) {
for (std::size_t i = 0; i < size && i < RHS::size; i++) {
if (value[i] != RHS::value[i]) {
return value[i] < RHS::value[i];
}
}
return size < RHS::size;
}
};
template <class CharT, CharT... Cs>
constexpr CharT const Value<CharT, Cs...>::value[sizeof...(Cs) + 1];
template <class CharT, CharT... Cs>
Value<CharT, Cs...> operator""_v() {
return {};
}
template <typename U, typename V>
auto min(U,V) -> std::conditional_t<(U{}<V{}), U, V>
{ return {}; }
int main() {
auto w = min("cde"_v, "abc"_v);
std::cout << typeid(w).name() << " : " << w.value << std::endl;
}
Output:
5ValueIcJLc97ELc98ELc99EEE : abc
Output of c++filt -t 5ValueIcJLc97ELc98ELc99EEE:
Value<char, (char)97, (char)98, (char)99>
[live demo]

calculating data compile time with template metaprogramming

Suppose we have code like this. It works well and pre-calculate first 5 Fibonacci numbers.
#include <iostream>
template <int T>
struct fib;
template <>
struct fib<0>{
constexpr static int value = 1;
};
template <>
struct fib<1>{
constexpr static int value = 1;
};
template <int I>
struct fib{
constexpr static int value = fib<I - 1>::value + fib<I - 2>::value;
};
int main(){
std::cout << fib<0>::value << std::endl;
std::cout << fib<1>::value << std::endl;
std::cout << fib<2>::value << std::endl;
std::cout << fib<3>::value << std::endl;
std::cout << fib<4>::value << std::endl;
std::cout << fib<5>::value << std::endl;
}
However there is "small" problem with it.
What if we need to use this for values, that are not known at compile time?
For few values we can do this:
const int max = 5;
int getData(){
return 5; // return value between 0 and max.
}
int something(){
switch(getData()){
case 0: return fib<0>::value;
case 1: return fib<1>::value;
case 2: return fib<2>::value;
case 3: return fib<3>::value;
case 4: return fib<4>::value;
case 5: return fib<5>::value;
}
}
This will works OK for 5 values, but what if we have 150 or 300?
Is not really serious to change the code with 300 rows...
What could be the workaround here?
If you need to use a value at runtime that isn't known at compile time, you can't compute it at compile time. Obvious.
But... if you can impose a top value to values needed, you can compute all values (from zero to top) at compile time and store them in an std::array.
In the following example I have modified your fib structs (to use a std::size_t index and a template type (with default unsigned long) for the value) and I have added a templated struct fibVals that contain an std::array that is initialized using fib<n>::value
The following main() show that is possible to define a constexpr fibvals<N> (with N == 20 in the example) to compute (at compile time) all fib<n> values in range [0,N[.
#include <array>
#include <utility>
#include <iostream>
template <std::size_t, typename T = unsigned long>
struct fib;
template <typename T>
struct fib<0U, T>
{ constexpr static T value { T(1) }; };
template <typename T>
struct fib<1U, T>
{ constexpr static T value { T(1) }; };
template <std::size_t I, typename T>
struct fib
{ constexpr static T value { fib<I-1U>::value + fib<I-2U>::value }; };
template <std::size_t I, typename T = unsigned long>
struct fibVals
{
const std::array<T, I> vals;
template <std::size_t ... Is>
constexpr fibVals ( std::index_sequence<Is...> const & )
: vals { { fib<Is, T>::value ... } }
{ }
constexpr fibVals () : fibVals { std::make_index_sequence<I> { } }
{ }
};
int main()
{
constexpr fibVals<20> fv;
for ( auto ui = 0U ; ui < fv.vals.size() ; ++ui )
std::cout << "fib(" << ui << ") = " << fv.vals[ui] << std::endl;
}
Unfortunately this example use std::make_index_sequence<I> and std::index_sequence<Is...> that are C++14 features.
If you want implement struct fibVals in C++11, you can implement the following structs struct indexSeq and struct indexSeqHelper, to substitute std::index_sequence<Is...> and std::make_index_sequence<I>
template <std::size_t ...>
struct indexSeq
{ };
template <std::size_t N, std::size_t ... Next>
struct indexSeqHelper
{ using type = typename indexSeqHelper<N-1U, N-1U, Next ... >::type; };
template <std::size_t ... Next >
struct indexSeqHelper<0U, Next ... >
{ using type = indexSeq<Next ... >; };
and implement fibVals constructors as follows
template <std::size_t ... Is>
constexpr fibVals ( indexSeq<Is...> const & )
: vals { { fib<Is, T>::value ... } }
{ }
constexpr fibVals () : fibVals { typename indexSeqHelper<I>::type { } }
{ }
Templates are evaluated at compile time, so there is no solution with templates that works at runtime.
You can make a constexpr function, which may be evaluated at compile time, depending on the value passed. Obviously, a runtime value may not be computed at compile time, as it is not known at compile time.

Variadic template pack expansion argument id

I am trying to make a variadic template function that reads elements in order (with an index). The goal is, for example to call the function read_tuple to read two ints with id 0 and 1 (with read_int(0) and read_int(1)).
Here is the code I get so far:
int data[] = {10,20,30,40};
int int_read(int id)
{
return data[id];
}
template <typename T>
T read(int& index)
{
index--;
int value = int_read(index);
std::cout << "index :" << index << " value: " << value << std::endl;
return value;
}
template <typename... Args>
std::tuple<Args...> read_tuple()
{
int index = sizeof...(Args);
return std::tuple<Args...>(read<Args>(index)...);
}
I can call it like that:
auto tuple = read_tuple<int, int>();
std::cout << "First: " << std::get<0>(tuple) << std::endl;
And I get the following output:
index :1 value: 20
index :0 value: 10
First: 10
However, this code is dependent of the order of evaluation of the read function. How can I generate an index dependent of the pack expansion (to avoid undefined behavior)?
As Piotr pointed out, the order of evaluation is garanteed if you use braced-init-list. Be carefull though if you use GCC prior to 4.9.1 as it does not work (c.f. Evaluation order (sequenced-before relation) among initializer-clauses in braced-init-list).
If you use a version that does not garantee the order of initialization (or just want to generate ids) you can use an indexer (from other stackoverflow post):
template<int... I> struct index {
template<int n> using append = index<I..., n>;
};
template<int N> struct make_index {
typedef typename make_index<N - 1>::type::template append<N - 1> type;
};
template<> struct make_index<0> { typedef index<> type; };
template<int N> using indexer = typename make_index<N>::type;
You can use it like that:
int data[] = {10,20,30,40};
int int_read(int id)
{
return data[id];
}
template <typename T>
T read(int index)
{
int value = int_read(index);
std::cout << "index :" << index << " value: " << value << std::endl;
return value;
}
template <typename... Args, int... i>
std::tuple<Args...> read_tuple_indexed(index<i...>)
{
return std::tuple<Args...>(read<Args>(i)...);
}
template <typename... Args>
std::tuple<Args...> read_tuple()
{
return read_tuple_indexed<Args...>(indexer<(sizeof...(Args))>());
}

Match a class by parameter type in a c++ template-generated class hierarchy

Intro
I am working on a custom memory allocator and need to add some bookkeeping info to the header of each allocated chunk. There are several different chunk types and the bookkeeping info differs as well. For instance, for chunks shared between threads there is a need to add a reference counter, for chunks used by single thread there is no such need. For chunks taken from a memory pool there is a need to keep a reference to the originating pool, for chunks taken from the free store there is no such need.
Problem
So I would like to have a generic interface to add and get certain data types for a given chunk layout. Experimenting with this idea I came to a solution that is similar to std::tuple. However unlike tuples every type I add to the header is going to be unique. I just started to learn template meta-programming and other intricacies of c++, however the part with adding a type was straightforward for me.
The problem I came across is implementing a method similar to C++14 std::get by type template function for tuples. I figured there is no need to write much code for this as the compiler is able to match the correct base class in a method call. At first I put the get method right into template-generated layout class. However the compiler fails to match the correct class in this case. The problem was solved by moving the get method to another, manually added, level of class hierarchy.
The code below demonstrates the problem. Defining HAVE_GET_IN_LAYOUT to 0 produces a working solution while defining it to 1 produces a broken solution [at least with clang++ 3.5 and 3.6]
The question is, what is broken in this case?
#include <cstddef>
#include <iostream>
#ifndef HAVE_GET_IN_LAYOUT
#define HAVE_GET_IN_LAYOUT 0
#endif
constexpr std::size_t Align(std::size_t size, std::size_t offset) {
return (size < 0x8
? (offset + 0x3) & ~0x3
: (size < 0x10 ? (offset + 0x7) & ~0x7 : (offset + 0xf) & ~0xf));
}
template <std::size_t Start, typename... Ts> struct Layout {
static constexpr std::size_t Size = 0;
static constexpr std::size_t Offset = Start;
static constexpr std::size_t TotalSize = Start;
};
template <std::size_t Start, typename T, typename... Ts>
struct Layout<Start, T, Ts...>
: public Layout<Align(sizeof(T), Start) + sizeof(T), Ts...> {
using Type = T;
static constexpr std::size_t Size = sizeof(Type);
static constexpr std::size_t Offset = Align(Size, Start);
static constexpr std::size_t TotalSize = Layout<Offset + Size, Ts...>::TotalSize;
Type value = Offset - Start; // no particular meaning, just for testing.
#if HAVE_GET_IN_LAYOUT
template <typename U, std::size_t X, typename... Us>
U &helper(Layout<X, U, Us...> *c) { return c->value; }
template <typename U> U &get() { return helper<U>(this); }
#endif
};
template <typename... Ts> struct Result : public Layout<0, Ts...> {
#if !HAVE_GET_IN_LAYOUT
template <typename U, std::size_t X, typename... Us>
U &helper(Layout<X, U, Us...> *c) { return c->value; }
template <typename U> U &get() { return helper<U>(this); }
#endif
};
int main() {
std::cout << "layout size <> = " << Layout<0>::TotalSize << std::endl;
std::cout << "layout size <int> = " << Layout<0, int>::TotalSize << std::endl;
std::cout << "layout size <long> = " << Layout<0, long>::TotalSize << std::endl;
std::cout << "layout size <int,int> = " << Layout<0, int, int>::TotalSize << std::endl;
std::cout << "layout size <int,long> = " << Layout<0, int, long>::TotalSize << std::endl;
std::cout << "layout size <long,int> = " << Layout<0, long, int>::TotalSize << std::endl;
std::cout << "layout size <long,long> = " << Layout<0, long, long>::TotalSize << std::endl;
std::cout << "get: " << Result<int, long, long double>{}.get<long>() << std::endl;
return 0;
}
So it looks like my code was perfectly legal, it is rather some clang++ problem. Alternatively I might be abusing some not well defined C++ behaviour. But this looks unlikely so far. If any C++ language lawyer could correct me, I would greatly appreciate this.
Anyway, I ended up with using my workaround which I enhanced after looking at some of the sample code provided at the question comments.
If anyone is interested how the real code using the described trick looks like, I'm pasting it here.
// Round down to a power of two multiple.
constexpr std::size_t Align(std::size_t n, std::size_t a) {
return n & ~(a - 1);
}
// Round up to a power of two multiple.
constexpr std::size_t AlignUp(std::size_t n, std::size_t a) {
return Align(n + a - 1, a);
}
namespace memory {
namespace detail {
// Calculate a data item alignment according to its size.
constexpr std::size_t Align(std::size_t size, std::size_t offset) {
return size < 0x08 ? ::AlignUp(offset, 0x04)
: size < 0x10 ? ::AlignUp(offset, 0x08)
: ::AlignUp(offset, 0x10);
}
// Services for placement of a given type instance within a memory chunk
// at the specified offset.
template <typename T, std::size_t S> class EntryLayout {
public:
using Type = T;
using Pointer = T *;
static constexpr std::size_t Size = sizeof(Type);
static constexpr std::size_t Offset = Align(Size, S);
static constexpr std::size_t EndOffset = Offset + Size;
static Pointer Instance(char *ptr) {
return reinterpret_cast<Pointer>(RawData(ptr));
}
template <typename... Args>
static Pointer Construct(char *ptr, Args &&... args) {
return new (RawData(ptr)) Type(std::forward<Args>(args)...);
}
static void Destruct(char *ptr) { Instance(ptr)->~Type(); }
private:
static char *RawData(char *ptr) { return ptr + Offset; }
};
// Services for placement of a number of types within a memory
// chunk at the specified offset.
template <std::size_t S, typename... Tail> class ChunkLayout {
public:
static constexpr std::size_t StartOffset = S;
static constexpr std::size_t EndOffset = S;
template <typename... Args> static void Construct(char *, Args...) {}
static void Destruct(char *) {}
};
// Recursive template specialization of the above.
template <std::size_t S, typename Head, typename... Tail>
class ChunkLayout<S, Head, Tail...>
: public ChunkLayout<EntryLayout<Head, S>::EndOffset, Tail...> {
public:
using EntryType = Head;
using HeadLayout = EntryLayout<Head, S>;
using TailLayout = ChunkLayout<HeadLayout::EndOffset, Tail...>;
static constexpr std::size_t StartOffset = S;
static constexpr std::size_t EndOffset = TailLayout::EndOffset;
static typename HeadLayout::Pointer Instance(char *ptr) {
return HeadLayout::Instance(ptr);
}
template <typename... Args> void Construct(char *ptr, Args... args) {
HeadLayout::Construct(ptr, args...);
TailLayout::Construct(ptr, args...);
}
void Destruct(char *ptr) {
TailLayout::Destruct(ptr);
HeadLayout::Destruct(ptr);
}
};
} // namespace detail
// Control of memory chunk free and used space.
class ChunkSpace {
public:
ChunkSpace(std::size_t size) noexcept : free_{size}, used_(0) {}
std::size_t Used() const { return used_; }
std::size_t Free() const { return free_; }
std::size_t Size() const { return free_ + used_; }
bool Alloc(std::size_t size) {
if (size > free_)
return false;
free_ -= size;
used_ += size;
return true;
}
void Reset(std::size_t size = 0) {
assert(size <= used_);
free_ = free_ + used_ - size;
used_ = size;
}
private:
std::size_t free_;
std::size_t used_;
};
template <typename... EntryType>
class Chunk : public detail::ChunkLayout<0, ChunkSpace, EntryType...> {
using Layout = detail::ChunkLayout<0, ChunkSpace, EntryType...>;
public:
Chunk(char *data, std::size_t size) : data_{data} {
assert(size > Layout::EndOffset);
// Construct ChunkSpace instance to bootstrap allocation.
Layout::HeadLayout::Construct(data_, size);
// Allocate space required for all the chunk data.
Alloc(Layout::EndOffset);
// Construct the rest of the chunk data.
Layout::TailLayout::Construct(data_);
}
~Chunk() {
Layout::Destruct(data_);
}
template <typename T>
T* Get() {
return decltype(Upcast<T>(this))::Instance(data_);
}
template <typename T>
const T* Get() const {
return decltype(Upcast<T>(this))::Instance(data_);
}
std::size_t Used() const { return Get<ChunkSpace>()->Used(); }
std::size_t Free() const { return Get<ChunkSpace>()->Free(); }
std::size_t Size() const { return Get<ChunkSpace>()->Size(); }
void *Allocate(std::size_t size) {
std::size_t offset = Used();
std::size_t aligned_offset = detail::Align(size, offset);
std::size_t offset_padding = aligned_offset - offset;
if (!Alloc(size + offset_padding))
return nullptr;
return data_ + aligned_offset;
}
private:
bool Alloc(std::size_t size) {
return Get<ChunkSpace>()->Alloc(size);
}
// Some C++ magic to upcast to the base class that contains layout info
// for a given entry type.
template <typename Head, std::size_t S, typename... Tail>
static typename detail::ChunkLayout<S, Head, Tail...>::HeadLayout
Upcast(const detail::ChunkLayout<S, Head, Tail...> *);
char *data_;
};
} // namespace memory
And now a sample use of all this machinery:
#include "chunk.h"
#include "iostream"
struct A {
int value = 0xa;
};
struct B {
int value = 0xb;
};
void alloc(memory::Chunk<A, B> &chunk, std::size_t size)
{
chunk.Allocate(size);
std::cout << "Allocate " << size << " bytes:" << std::endl;
std::cout << " used: " << chunk.Used() << std::endl;
std::cout << " free: " << chunk.Free() << std::endl;
}
int main()
{
char buffer[1024];
memory::Chunk<A, B> chunk(buffer, sizeof buffer);
std::cout << "used: " << chunk.Used() << std::endl;
std::cout << "free: " << chunk.Free() << std::endl;
A *a = chunk.Get<A>();
B *b = chunk.Get<B>();
std::cout << std::hex;
std::cout << "a: " << a->value << " b: " << b->value << std::endl;
std::cout << std::dec;
alloc(chunk, 1);
alloc(chunk, 2);
alloc(chunk, 4);
alloc(chunk, 8);
alloc(chunk, 16);
return 0;
}