I need to write a class, that contains a fixed length array (length of array defined by template argument), and the array must be initialized right away, with the restriction, that every member is initialized. Also note, i am using c++17.
I am not too familiar with the full capabilities of c++'s templates, but i really would like to re-implement this functionality from pure C as managing multiple instances of this data structure is getting tiresome.
Here is the sample code:
#include <iostream>
enum TraitEnum
{
Trait_0,
Trait_1,
Trait_2,
Trait_3,
Trait_N,
};
template<typename TraitType, TraitType NType>
class TraitField
{
public:
struct TraitStruct
{
TraitType Trait;
bool Status;
};
TraitField(const TraitStruct TraitArray[NType]) :
traitArray{ TraitArray }
{}
private:
TraitStruct traitArray[NType];
};
int main()
{
TraitField<TraitEnum, Trait_N> myTraitField({
{ Trait_0, true },
{ Trait_1, true },
{ Trait_2, true },
{ Trait_3, true },
});
std::cout << "test" << std::endl;
return 0;
}
The compiler gives the following error:
error C2664: 'TraitField<TraitEnum,Trait_N>::TraitField(TraitField<TraitEnum,Trait_N> &&)': cannot convert argument 1 from 'initializer list' to 'const TraitField<TraitEnum,Trait_N>::TraitStruct []'
I might be able to initialize the array with initializer-list, but then won't I lose the restriction that an array of exact same size must be passed? It is really important for me, that the array inside the class is fully initialized at compile time.
Also i am not sure, if the compiler can deduce the correct type of the unnamed array, that I'm passing to the constructor.
EDIT: forgot to mention, for project restrictions, i can NOT use the standard template library, so no std::vector
or std::array
is allowed.
EDIT2: after defining a custom container type for the array it works:
#include <iostream>
enum TraitEnum
{
Trait_0,
Trait_1,
Trait_2,
Trait_3,
Trait_N,
};
template<typename ElemType, size_t NElem>
struct ArrayType
{
ElemType data[NElem];
};
template<typename TraitType, TraitType NType>
class TraitField
{
public:
struct TraitStruct
{
TraitType Trait;
bool Status;
};
typedef ArrayType<TraitStruct, NType> TraitArrayType;
TraitField(const TraitArrayType &TraitArray) :
traitArray{ TraitArray }
{}
private:
TraitArrayType traitArray;
};
int main()
{
TraitField<TraitEnum, Trait_N>::TraitArrayType testArray{
{
{ Trait_0, true },
{ Trait_1, true },
{ Trait_2, true },
{ Trait_3, true },
}
};
TraitField<TraitEnum, Trait_N> myTraitField(testArray);
std::cout << "test" << std::endl;
return 0;
}
One more this is that i would like to avoid to declare the "testArray
" if possible, but if i initialize the object directly with the unnamed arrays, the compiler tries to convert it directly to initializer list instead of an array of my defined type.
EDIT3: Thanks max66, your solution seems to be exactly what i wanted. I just made some modifications, namely needed to re-impletement the make_index_sequence and index_sequence from here: details of std::make_index_sequence and std::index_sequence (needed to strip away the std::decay part too, since this will only hold primitive types) also need the object to be non-constexpr, since i need to modify it runtime (reflected in sample code)
#include <iostream>
enum TraitEnum
{
Trait_0,
Trait_1,
Trait_2,
Trait_3,
Trait_N,
};
template<typename TraitType, TraitType NType>
class TraitField
{
public:
template <std::size_t... Ns>
struct index_sequence {};
template <std::size_t N, std::size_t... Is>
auto make_index_sequence_impl()
{
if constexpr (N == 0) // stop condition
{
return index_sequence<Is...>();
}
else // recursion
{
return make_index_sequence_impl<N - 1, N - 1, Is...>();
}
}
template <std::size_t N>
using make_index_sequence = decltype(make_index_sequence_impl<N>());
struct TraitStruct
{
TraitType Trait;
bool Status;
};
constexpr TraitField(TraitStruct const (&arr)[NType])
: TraitField{ arr, std::make_index_sequence<NType>{} }
{ }
public:
TraitStruct traitArray[NType];
template <std::size_t ... Is>
constexpr TraitField(TraitStruct const (&arr)[NType],
std::index_sequence<Is...>)
: traitArray{ arr[Is]... }
{ }
};
int main()
{
TraitField<TraitEnum, Trait_N> myTraitField{ {
{ Trait_0, true },
{ Trait_1, true },
{ Trait_2, true },
{ Trait_3, true },
} };
for (auto trait : myTraitField.traitArray)
{
std::cout << trait.Trait << " " << trait.Status << std::endl;
}
std::cout << std::endl;
myTraitField.traitArray[Trait_1].Status = false;
for (auto trait : myTraitField.traitArray)
{
std::cout << trait.Trait << " " << trait.Status << std::endl;
}
return 0;
}
If you can use std::make_index_sequence
and std::index_sequence
, you can combine them with a constructor receiving a C-style array of TraitStruct
s and a delegating constructor and write something as follows
#include <utility>
#include <iostream>
enum TraitEnum
{ Trait_0, Trait_1, Trait_2, Trait_3, Trait_N, };
template <typename TraitType, TraitType NType>
class TraitField
{
public:
struct TraitStruct
{
TraitType Trait;
bool Status;
};
private:
TraitStruct traitArray[NType];
template <std::size_t ... Is>
constexpr TraitField (TraitStruct const (&arr)[NType],
std::index_sequence<Is...>)
: traitArray{ arr[Is]... }
{ }
public:
constexpr TraitField (TraitStruct const (&arr)[NType])
: TraitField{arr, std::make_index_sequence<NType>{}}
{ }
};
int main ()
{
constexpr TraitField<TraitEnum, Trait_N> myTraitField { {
{ Trait_0, true }, { Trait_1, true },
{ Trait_2, true }, { Trait_3, true },
} };
}
Observe that, as you required (" is really important for me, that the array inside the class is fully initialized at compile time"), myTraitField
is declared constexpr
, so it's initialized compile time (this isn't true in your "EDIT2" example).
-- EDIT --
If you need something to replace std::index_sequence
and std::make_index_sequence
, given that you can use C++17 so also if constexpr
, I propose the following logarithmic version
#include <utility>
#include <type_traits>
template <std::size_t ...>
struct my_index_sequence
{ };
template <typename, typename>
struct append_sequences;
template <std::size_t ... Is1, std::size_t ... Is2>
struct append_sequences<my_index_sequence<Is1...>,
my_index_sequence<Is2...>>
{ using type = my_index_sequence<Is1..., sizeof...(Is1)+Is2...>; };
template <std::size_t N>
auto mmis_helper ()
{
if constexpr ( 0u == N )
return my_index_sequence<>{};
else if constexpr ( 1u == N )
return my_index_sequence<0u>{};
else
return typename append_sequences<
decltype(mmis_helper<(N >> 1)>()),
decltype(mmis_helper<N - (N >> 1)>())>::type {};
}
template <std::size_t N>
using my_make_index_sequence = decltype(mmis_helper<N>());
int main ()
{
using T1 = my_make_index_sequence<13u>;
using T2 = my_index_sequence<0u, 1u, 2u, 3u, 4u, 5u, 6u, 7u, 8u, 9u,
10u, 11u, 12u>;
static_assert(std::is_same_v<T1, T2>);
}