c++ - 在模板类中初始化一个固定长度的数组

标签 c++ arrays templates c++17 static-initialization

我需要编写一个类,它包含一个固定长度的数组(由模板参数定义的数组长度),并且必须立即初始化该数组,并限制每个成员都被初始化。
另请注意,我使用的是 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 []'

我也许可以使用初始化器列表来初始化数组,但是我不会失去必须传递完全相同大小的数组的限制吗?对我来说非常重要的是,类中的数组在编译时完全初始化。

我也不确定,如果编译器可以推断出未命名数组的正确类型,我将传递给构造函数。

编辑:忘了提,由于项目限制,我不能使用标准模板库,所以没有 std::vectorstd::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”如果可能的话,但是如果我直接用未命名的数组初始化对象,编译器会尝试将它直接转换为初始化列表而不是我定义的数组类型。

编辑3:
感谢 max66,您的解决方案似乎正是我想要的。
我只是做了一些修改,即需要从这里重新实现 make_index_sequence 和 index_sequence:details of std::make_index_sequence and std::index_sequence (也需要去掉 std::decay 部分,因为它只包含原始类型)
还需要对象是非常数的,因为我需要在运行时修改它(反射(reflect)在示例代码中)

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

最佳答案

如果可以使用 std::make_index_sequencestd::index_sequence ,您可以将它们与接收 C 样式数组 TraitStruct 的构造函数结合起来。 s 和委托(delegate)构造函数并编写如下内容

#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 ,因此它是初始化的编译时间(在您的“EDIT2”示例中并非如此)。

-- 编辑 --

如果您需要更换std::index_sequencestd::make_index_sequence , 假设你可以使用 C++17 所以也可以使用 if constexpr ,我提出以下对数版本
#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>);
 }

关于c++ - 在模板类中初始化一个固定长度的数组,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59791020/

相关文章:

C++:我怎样才能重载一个函数,以便它可以接受任何仿函数,包括指向成员函数的指针?

c++ - 使用模板时避免循环依赖问题

c++ - 根据给定的索引集获取参数包的子集

c++ - 为动态创建的 TComponent 派生类实例传递 NULL Owner 参数是否可行?

c++ - 为什么我必须把这个函数静态化

c++ - 帮助算法动态更新文本显示

c++ - 如何编译动态库?

c - 查找数组大小时出错

java - 这样做的正确方法是什么?

python - 如何从无关的嵌套中清除 Python 列表?