I started to implement a very flexible Odometer. It may have several disks with even different amount of values on each different disk. And, as an extension, even the data type of values on each of the single disks could be different.
All this shall be implemented with one class. The number of template parameters defines the behavior of the class.
Odomoter<int>
shall result in an Odometer having int
values on each disk. The resulting internal data type will be a std::vector<std::vector<int>>
Odometer<char, int, double>
, this will result in a data type std::tuple<std::vector<char>, std::vector<int>, std::vector<double>>
Now, I want to add a variadic constructor, where I can add whatever data. Of course types and number of arguments must match. I omit the check for the moment and will add it later.
So, now I have a templatized variadic class and a variadic constructor. So, I have the parameter pack off the class and the parameter pack of the constructor.
Now I would need iterate over the elements of both parameter packs at the same time in parallel.
Please see the below code example for an illustration of the problem (I deleted most of the code in the class, to just show you the problem):
#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});
}
I can iterate over the parameter pack of the constructor using a fold expression. I could also use std::apply
. But, I need to iterate also over the tuple elements of the "disks", defined by the class template parameters.
I do not want to use recursive templates.
So, I need to iterate of 2 parameter packs in parallel at the same time. How could this be done?
The only idea I have now is to use a helper class with a std::index_sequence
, but I do not know.
Please be reminded. Check of number of elements in parameter packs and type will be done later.
The only idea I have now is to use a helper class with a
std::index_sequence
, but I do not know.
You can use template lambda to expand index_sequence
and get the corresponding tuple elements through 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...>{});