1

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;
}
user2281752
  • 145
  • 1
  • 9
  • 7
    [Pro Tip] Use `std::array` in place of raw arrays. It actually has value semantics unlike a raw array. – NathanOliver Jan 17 '20 at 16:13
  • Edited post, to reflect the fact, that I cannot use anything from STL due to project restrictions. Otherwise i would have used array myself. – user2281752 Jan 17 '20 at 16:34
  • 1
    Then that is a bad project. – underscore_d Jan 17 '20 at 16:39
  • 2
    `std::array` is fairly trivial to implement yourself. Depending on all the functionality you want, it can be as simple as `template struct my_array { T data[N]; };` – NathanOliver Jan 17 '20 at 16:40
  • Edited with a working version, but there are some redundant declarations i would like to avoid. – user2281752 Jan 17 '20 at 17:55
  • 1
    If you need to create a `std::make_index_sequence` like, take in count that my answer in the cited question is good to give a simple description of a possible implementation but isn't a good implementation (it's linear, so you break the template recursion limit with low numbers; better a logarithmic implementation). – max66 Jan 20 '20 at 09:57
  • @max66, could you please give an example as how to implement it logarithmically? As the example above shows, I'm a bit inexperienced in template programming, and it would help tremendously. – user2281752 Jan 20 '20 at 12:11
  • Answer improved with a logarithmic `my_make_index_sequence` – max66 Jan 20 '20 at 12:18

1 Answers1

2

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 TraitStructs 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>);
 }
max66
  • 65,235
  • 10
  • 71
  • 111
  • Thanks, this seems to be exactly what i needed. Also i removed the constexpr from myTraitField, because i need to be able to modify the states: `TraitField myTraitField{ { { Trait_0, true }, { Trait_1, true }, { Trait_2, true }, { Trait_3, true }, } };` – user2281752 Jan 20 '20 at 09:48