【发布时间】: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::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”,但是如果我直接使用未命名的数组初始化对象,编译器会尝试将其直接转换为初始化列表而不是数组我定义的类型。
编辑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 <typename T, std::size_t N> struct my_array { T data[N]; };一样简单 -
如果您需要创建
std::make_index_sequence之类的,请考虑到我在引用问题中的回答很适合对可能的实现进行简单描述,但不是一个好的实现(它是线性的,所以你用低数字打破了模板递归限制;更好的对数实现)。
标签: c++ arrays templates c++17 static-initialization