template <typename T, std::size_t TAMANYO>
struct arreglo
{
using value_type = T; // (1)
using arreglo_type = arreglo;
template <std::size_t INICIO, std::size_t SUB_TAMANYO>
struct sub_arreglo;
{
using value_type = arreglo_type::value_type *; // (2)
static constexpr auto inicio = INICIO;
static constexpr auto sub_tamanyo = SUB_TAMANYO;
value_type datos[sub_tamanyo]{}; // (3)
};
};
The design you are proposing would force you to use sub_arreglo
in the following way:
struct arreglo<int,10>::sub_arreglo<2,3> x = a.sub<2,2>();
Having the two nested structures can complicate certain operations with class sub_arreglo
. Thus, it may not be trivial to make a comparison between two subsets if the initial arrangement has a different size:
arreglo<int,10> a;
arreglo<int,20> b;
arreglo<int,10>::sub_arreglo<2,2> x = a.sub<2,2>();
arreglo<int,20>::sub_arreglo<2,2> y = b.sub<2,2>();
if( x == y ) // x e y son de tipos distintos
// ...
The advisable thing in this case is that sub_arreglo
is an independent class of arreglo
, in such a way that:
arreglo<int,10> a;
arreglo<int,20> b;
sub_arreglo<int,2,2> x = a.sub<2,2>();
sub_arreglo<int,2,2> y = b.sub<2,2>();
if( x == y ) // x e y son del mismo tipo
// ...
What happens now is that sub_arreglo
needs to explicitly know the type of data to which it is going to point:
template <class T, std::size_t INICIO, std::size_t TAMANYO>
struct sub_arreglo
{
// ...
};
The problem that we can find then is that if two sub_arreglo
have different beginnings then their types will be different even if the size is the same:
arreglo<int,10> a;
arreglo<int,20> b;
sub_arreglo<int,2,2> x = a.sub<2,2>();
sub_arreglo<int,4,2> y = b.sub<4,2>();
if( x == y ) // x e y son de tipos distintos
// ...
This implies that the start position should not be part of sub_arreglo
:
template <class T, std::size_t TAMANYO>
struct sub_arreglo
{
// ...
};
You comment that you would like the result to be calculated at compile time. Well, this is not possible because the values of arreglo
are not known at compile time. There you are not manipulating types but editable values by the user, then the precalculus in compile time is not possible.
One possible solution would be then:
template <class T, std::size_t TAMANYO>
struct sub_arreglo
{
using value_type = T*;
static constexpr auto tamanyo = TAMANYO;
value_type datos[TAMANYO];
template<class Iterator>
sub_arreglo(Iterator begin, Iterator end)
{
for( auto i = 0; begin!=end; ++begin, ++i)
{
datos[i] = &(*begin);
}
}
};
template <typename T, std::size_t TAMANYO>
struct arreglo
{
T datos[TAMANYO];
static constexpr auto tamanyo = TAMANYO;
template<size_t INICIO, size_t SUB_TAMANYO>
sub_arreglo<T,SUB_TAMANYO> sub()
{
auto it = std::next(std::begin(datos),INICIO);
auto itEnd = std::next(it,SUB_TAMANYO);
return sub_arreglo<T,SUB_TAMANYO>(it,itEnd);
}
};
int main() {
arreglo<int,10> a;
for( auto i=0u; i<a.tamanyo; i++)
a.datos[i] = i;
auto x = a.sub<2,2>();
for( auto i=0u; i<x.tamanyo; i++)
std::cout << *x.datos[i] << '\n';
}
To create ranges at compile time you can take advantage of variadic templates . I give you an example to take ranges of numbers:
template<class T, size_t... Args> struct Array {
static const T data[sizeof...(Args)];
};
template<class T, size_t... Args>
const T Array<T,Args...>::data[sizeof...(Args)] = { static_cast<T>(Args)... };
template<size_t Contador, class T, size_t Actual, size_t... Args>
struct GeneradorArrayImpl {
using result = typename GeneradorArrayImpl<Contador-1, T, Actual+1, Args..., Actual>::result;
};
template<class T, size_t Actual, size_t... Args>
struct GeneradorArrayImpl<0, T, Actual, Args...> {
using result = Array<T, Args...>;
};
template<class T, size_t Inicio, size_t Total>
struct GeneradorArray {
using type = typename GeneradorArrayImpl<Total,T,Inicio>::result;
};
int main() {
const size_t inicio = 2;
const size_t total = 8;
using array = GeneradorArray<int,inicio,total>::type;
for (size_t i=0; i<total; ++i)
std::cout << array::data[i] << '\n';
}
How does the previous example work? As follows:
GeneradorArray<int,inicio,total>
becomes GeneradorArray<int,2,8>
. GeneradorArray<int,2,8>::type
is calculated from GeneradorArrayImpl<8,int,2>::result
. This call generates the following recursive sequence:
-
GeneradorArrayImpl<8,int,2>::result = GeneradorArrayImpl<7,int,3,2>::result
For next steps I omit equality for readability
-
GeneradorArrayImpl<7,int,3,2>
-
GeneradorArrayImpl<6,int,4,2,3>
-
GeneradorArrayImpl<5,int,5,2,3,4>
-
GeneradorArrayImpl<4,int,6,2,3,4,5>
-
GeneradorArrayImpl<3,int,7,2,3,4,5,6>
-
GeneradorArrayImpl<2,int,8,2,3,4,5,6,7>
-
GeneradorArrayImpl<1,int,9,2,3,4,5,6,7,9>
-
GeneradorArrayImpl<0,int,10,2,3,4,5,6,7,8,9>
.
The last template of the previous sequence corresponds to the specialization GeneradorArrayImpl<0,T,Actual,Args...>
, which in our case will be GeneradorArrayImpl<0,int,10,2,3,4,5,6,7,8,9>
. The result
of this template is calculated by calling Array<T,Args...>
, that is:
GeneradorArrayImpl<0,int,10,2,3,4,5,6,7,8,9>::result = Array<int,2,3,4,5,6,7,8,9>
So, back in the recursive sequence of GeneradorArrayImpl
, we have the following:
GeneradorArrayImpl<8,int,2,2>::result = Array<int,2,3,4,5,6,7,8,9>;
Array<>
has a member data
that is an array of size sizeof...(Args)
, sizeof...
returns the number of elements in the expansion list. The actual call to the template is, in this case, Array<int,2,3,4,5,6,7,8,9>
, which implies that the expansion list has 8 elements. The arrangement will be size 8.
When we instantiate this type:
using array = GeneradorArray<int,inicio,total>::type; // Creamos un alias por comodidad
for (size_t i=0; i<total; ++i)
std::cout << array::data[i] << '\n'; // <<--- AQUI se instancia
initialization of static member data
is called:
template<class T, size_t... Args>
const T Array<T,Args...>::data[sizeof...(Args)] = { static_cast<T>(Args)... };
That if we specialize it looks like this:
template<int,2,3,4,5,6,7,8,9>
const int Array<int,2,3,4,5,6,7,8,9>::data[8] = { static_cast<int>(Args)... };
What happens here is the following:
The code:
{ static_cast<int>(Args)... };
It will expand as follows:
{ static_cast<int>(2), static_cast<int>(3), ..., static_cast<int>(9) };
That is, you are creating an initialization list with the desired range of values and assigning it to the array data
.
At the end of the process it is as if we had done the following:
struct A
{
static const int data[8];
};
const int data[8] = { 2,3,4,5,6,7,8,9 };
int main() {
const size_t inicio = 2;
const size_t total = 8;
for (size_t i=0; i<total; ++i)
std::cout << A::data[i] << '\n';
}