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;
}
8
  • 7
    [Pro Tip] Use std::array in place of raw arrays. It actually has value semantics unlike a raw array. Commented Jan 17, 2020 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. Commented Jan 17, 2020 at 16:34
  • 1
    Then that is a bad project. Commented Jan 17, 2020 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 <typename T, std::size_t N> struct my_array { T data[N]; }; Commented Jan 17, 2020 at 16:40
  • 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). Commented Jan 20, 2020 at 9:57

1 Answer 1

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>);
 }
Sign up to request clarification or add additional context in comments.

1 Comment

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<TraitEnum, Trait_N> myTraitField{ { { Trait_0, true }, { Trait_1, true }, { Trait_2, true }, { Trait_3, true }, } };

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.