我开始实现一个非常灵活的里程表。它可能有几个磁盘,每个不同的磁盘上的值数量甚至不同。而且,作为扩展,甚至每个磁盘上值的数据类型也可能不同。
这一切都用一个班来实现。模板参数的数量定义了类的行为。
Odomoter<int>
应导致里程表在每个磁盘上具有 int
值。生成的内部数据类型将是 std::vector<std::vector<int>>
Odometer<char, int, double>
的情况下,这将导致数据类型std::tuple<std::vector<char>, std::vector<int>, std::vector<double>>
现在,我想添加一个可变参数构造函数,我可以在其中添加任何数据。当然,参数的类型和数量必须匹配。我暂时省略检查,稍后再添加。
所以,现在我有了一个模板化的可变参数类和一个可变参数构造函数。所以,我有类的参数包和构造函数的参数包。
现在我需要同时并行迭代两个参数包的元素。
请参阅下面的代码示例以说明问题(我删除了课程中的大部分代码,只是为了向您展示问题):
#include <vector>
#include <tuple>
#include <list>
#include <initializer_list>
template<typename...Ts>
struct Odometer {
static constexpr bool IsTuple = ((std::tuple_size<std::tuple<Ts...>>::value) > 1);
template<typename...Ts>
using Tuples = std::tuple<std::vector<Ts>...>;
template<typename...Ts>
using MyType = std::tuple_element_t<0, std::tuple<Ts...>>;
template<typename...Ts>
using Vectors = std::vector<std::vector<MyType<Ts...>>>;
template<typename...Ts>
using Disks = std::conditional<IsTuple, Tuples<Ts...>, Vectors<Ts...>>::type;
Disks<Ts...> disks{};
template <typename...Args>
Odometer(Args...args) {
if constexpr (IsTuple) {
// Here disk is a std::tuple<std::vector<char>, std::vector<int>, std::vector<double>>
([&] {
//std::vector<MyType<Ts...>> disk{}; // Does not work. Or would always be a std::vector<char>
if constexpr (std::ranges::range<Args>) {
//for (const auto& r : args) // Does not work
//disk.push_back(r); // Does not work
}
else {
//disk.push_back(args); // Does not work
} } (), ...);
}
else {
([&] {
disks.push_back({});
if constexpr (std::ranges::range<Args>) {
for (const auto& r : args)
disks.back().push_back(r);
}
else {
disks.back().push_back(args);
} } (), ...);
}
}
};
int main() {
Odometer<char, int, double> odo2('a', std::vector{1,2,3}, std::list{4.4, 5.5});
}
我可以使用折叠表达式迭代构造函数的参数包。我也可以使用
std::apply
。但是,我还需要遍历由类模板参数定义的“磁盘”的元组元素。
我不想使用递归模板。
所以,我需要同时并行迭代2个参数包。这怎么可能?
我现在唯一的想法是使用带有
std::index_sequence
的辅助类,但我不知道。
请注意。稍后将检查参数包中的元素数量和类型。
我现在唯一的想法是使用一个带有
,但我不知道。std::index_sequence
可以使用template lambda展开
index_sequence
,通过std::get<Is>
得到对应的元组元素:
static_assert(sizeof...(Args) == sizeof...(Ts));
[&]<std::size_t... Is>(std::index_sequence<Is...>) {
([&] {
auto& disk = std::get<Is>(disks);
if constexpr (std::ranges::range<Args>)
for (const auto& elem : args)
disk.push_back(elem);
else
disk.push_back(args);
} (), ...);
}(std::index_sequence_for<Args...>{});
你不需要手动迭代,你只需要一个函数将每个参数转换成向量:
template <typename T, typename Arg>
std::vector<T> as_vector(Arg&& arg)
{
if constexpr (std::ranges::range<std::decay_t<Arg>>) {
return {std::begin(arg), std::end(arg)};
} else {
return {std::forward<Arg>(arg)};
}
}
然后你的构造函数很简单(应该更复杂:SFINAE for nearly copy constructor with forwarding reference to avoid current copies):
template <typename...Args>
Odometer(Args... args) : disks{as_vector<Ts>(args)...} {}
请注意,
as_vector<Ts>(args)...
将两个包与一个 ...
一起使用,因此它们的尺寸应该相同。