Инициализация массива фиксированной длины внутри класса шаблона - PullRequest
1 голос
/ 17 января 2020

Мне нужно написать класс, который содержит массив фиксированной длины (длина массива определяется аргументом шаблона), и массив должен быть инициализирован сразу, с ограничением, что каждый член инициализируется. Также обратите внимание, что я использую c ++ 17.

Я не слишком знаком со всеми возможностями шаблонов c ++, но я действительно хотел бы повторно реализовать эту функциональность из чистого C для управления несколькими экземплярами. этой структуры данных становится утомительным.

Вот пример кода:

#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;
}

Компилятор выдает следующую ошибку:

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 []'

Я мог бы инициализировать массив с помощью initializer-list, но не потеряю ли я ограничение на то, что необходимо передать массив точно такого же размера? Для меня очень важно, чтобы массив внутри класса полностью инициализировался во время компиляции.

Также я не уверен, может ли компилятор определить правильный тип безымянного массива, который я передаю в конструктор.

РЕДАКТИРОВАТЬ: забыл упомянуть, для ограничений проекта, я не могу использовать стандартную библиотеку шаблонов, поэтому std::vector или std::array не допускается.

EDIT2: после определяя пользовательский тип контейнера для массива, он работает:

#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;
}

Еще одно, это то, что я хотел бы избежать объявления "testArray", если это возможно, но если я инициализирую объект непосредственно с помощью безымянные массивы, компилятор пытается преобразовать его непосредственно в список инициализаторов вместо массива моего определенного типа.

EDIT3: Спасибо, max66, ваше решение, похоже, именно то, что я хотел. Я только что сделал некоторые изменения, а именно, необходимо повторно реализовать make_index_sequence и index_sequence отсюда: подробности о std :: make_index_sequence и std :: index_sequence (необходимо также убрать часть std :: decay, поскольку он будет содержать только примитивные типы) также нужно, чтобы объект не был constexpr, так как мне нужно изменить его время выполнения (отражено в примере кода)

#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;
}

1 Ответ

2 голосов
/ 18 января 2020

Если вы можете использовать std::make_index_sequence и std::index_sequence, вы можете объединить их с конструктором, получившим массив C в стиле TraitStruct s, и делегирующим конструктором и написать что-то следующим образом

#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 },
   } };
 }

Заметьте, что, как вы и требовали («для меня действительно важно, чтобы массив внутри класса был полностью инициализирован во время компиляции»), myTraitField объявлен constexpr, поэтому он инициализируется во время компиляции (это не t истина в вашем примере "EDIT2").

- EDIT -

Если вам нужно что-то заменить std::index_sequence и std::make_index_sequence, учитывая, что вы можно использовать C ++ 17, поэтому также if constexpr, я предлагаю следующий логарифми c версия

#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>);
 }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...