【问题标题】:Initializing a fixed length array inside a template class在模板类中初始化一个固定长度的数组
【发布时间】:2020-01-17 16:11:40
【问题描述】:

我需要编写一个类,它包含一个固定长度的数组(数组的长度由模板参数定义),并且必须立即初始化该数组,并限制每个成员都被初始化。 另请注意,我使用的是 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 部分,因为它只包含原始类型) 还需要对象是非常数的,因为我需要在运行时修改它(反映在示例代码中)

#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::array 代替原始数组。它实际上具有不同于原始数组的值语义。
  • 已编辑帖子,以反映一个事实,即由于项目限制,我无法使用 STL 中的任何东西。否则我会自己使用数组。
  • 那是一个糟糕的项目。
  • std::array 自己实现起来相当简单。根据您想要的所有功能,它可以像template &lt;typename T, std::size_t N&gt; struct my_array { T data[N]; }; 一样简单
  • 如果您需要创建 std::make_index_sequence 之类的,请考虑到我在引用问题中的回答很适合对可能的实现进行简单描述,但不是一个好的实现(它是线性的,所以你用低数字打破了模板递归限制;更好的对数实现)。

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


【解决方案1】:

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

#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”示例中为真)。

-- 编辑--

如果您需要替换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>);
 }

【讨论】:

  • 谢谢,这似乎正是我所需要的。我还从 myTraitField 中删除了 constexpr,因为我需要能够修改状态:TraitField&lt;TraitEnum, Trait_N&gt; myTraitField{ { { Trait_0, true }, { Trait_1, true }, { Trait_2, true }, { Trait_3, true }, } };
猜你喜欢
  • 2021-08-23
  • 1970-01-01
  • 2014-04-02
  • 1970-01-01
  • 1970-01-01
  • 2020-04-17
  • 2020-04-12
  • 1970-01-01
相关资源
最近更新 更多