另一种选择是为元组实现迭代器。这样做的好处是您可以使用标准库和基于范围的 for 循环提供的各种算法。此处https://foonathan.net/2017/03/tuple-iterator/ 解释了一种优雅的方法。基本思想是使用begin() 和end() 方法将元组变成一个范围以提供迭代器。迭代器本身返回一个std::variant<...>,然后可以使用std::visit 访问它。
这里有一些例子:
auto t = std::tuple{ 1, 2.f, 3.0 };
auto r = to_range(t);
for(auto v : r)
{
std::visit(unwrap([](auto& x)
{
x = 1;
}), v);
}
std::for_each(begin(r), end(r), [](auto v)
{
std::visit(unwrap([](auto& x)
{
x = 0;
}), v);
});
std::accumulate(begin(r), end(r), 0.0, [](auto acc, auto v)
{
return acc + std::visit(unwrap([](auto& x)
{
return static_cast<double>(x);
}), v);
});
std::for_each(begin(r), end(r), [](auto v)
{
std::visit(unwrap([](const auto& x)
{
std::cout << x << std::endl;
}), v);
});
std::for_each(begin(r), end(r), [](auto v)
{
std::visit(overload(
[](int x) { std::cout << "int" << std::endl; },
[](float x) { std::cout << "float" << std::endl; },
[](double x) { std::cout << "double" << std::endl; }), v);
});
我的实现(很大程度上基于上面链接中的解释):
#ifndef TUPLE_RANGE_H
#define TUPLE_RANGE_H
#include <utility>
#include <functional>
#include <variant>
#include <type_traits>
template<typename Accessor>
class tuple_iterator
{
public:
tuple_iterator(Accessor acc, const int idx)
: acc_(acc), index_(idx)
{
}
tuple_iterator operator++()
{
++index_;
return *this;
}
template<typename T>
bool operator ==(tuple_iterator<T> other)
{
return index_ == other.index();
}
template<typename T>
bool operator !=(tuple_iterator<T> other)
{
return index_ != other.index();
}
auto operator*() { return std::invoke(acc_, index_); }
[[nodiscard]] int index() const { return index_; }
private:
const Accessor acc_;
int index_;
};
template<bool IsConst, typename...Ts>
struct tuple_access
{
using tuple_type = std::tuple<Ts...>;
using tuple_ref = std::conditional_t<IsConst, const tuple_type&, tuple_type&>;
template<typename T>
using element_ref = std::conditional_t<IsConst,
std::reference_wrapper<const T>,
std::reference_wrapper<T>>;
using variant_type = std::variant<element_ref<Ts>...>;
using function_type = variant_type(*)(tuple_ref);
using table_type = std::array<function_type, sizeof...(Ts)>;
private:
template<size_t Index>
static constexpr function_type create_accessor()
{
return { [](tuple_ref t) -> variant_type
{
if constexpr (IsConst)
return std::cref(std::get<Index>(t));
else
return std::ref(std::get<Index>(t));
} };
}
template<size_t...Is>
static constexpr table_type create_table(std::index_sequence<Is...>)
{
return { create_accessor<Is>()... };
}
public:
static constexpr auto table = create_table(std::make_index_sequence<sizeof...(Ts)>{});
};
template<bool IsConst, typename...Ts>
class tuple_range
{
public:
using tuple_access_type = tuple_access<IsConst, Ts...>;
using tuple_ref = typename tuple_access_type::tuple_ref;
static constexpr auto tuple_size = sizeof...(Ts);
explicit tuple_range(tuple_ref tuple)
: tuple_(tuple)
{
}
[[nodiscard]] auto begin() const
{
return tuple_iterator{ create_accessor(), 0 };
}
[[nodiscard]] auto end() const
{
return tuple_iterator{ create_accessor(), tuple_size };
}
private:
tuple_ref tuple_;
auto create_accessor() const
{
return [this](int idx)
{
return std::invoke(tuple_access_type::table[idx], tuple_);
};
}
};
template<bool IsConst, typename...Ts>
auto begin(const tuple_range<IsConst, Ts...>& r)
{
return r.begin();
}
template<bool IsConst, typename...Ts>
auto end(const tuple_range<IsConst, Ts...>& r)
{
return r.end();
}
template <class ... Fs>
struct overload : Fs... {
explicit overload(Fs&&... fs) : Fs{ fs }... {}
using Fs::operator()...;
template<class T>
auto operator()(std::reference_wrapper<T> ref)
{
return (*this)(ref.get());
}
template<class T>
auto operator()(std::reference_wrapper<const T> ref)
{
return (*this)(ref.get());
}
};
template <class F>
struct unwrap : overload<F>
{
explicit unwrap(F&& f) : overload<F>{ std::forward<F>(f) } {}
using overload<F>::operator();
};
template<typename...Ts>
auto to_range(std::tuple<Ts...>& t)
{
return tuple_range<false, Ts...>{t};
}
template<typename...Ts>
auto to_range(const std::tuple<Ts...>& t)
{
return tuple_range<true, Ts...>{t};
}
#endif
通过将const std::tuple<>& 传递给to_range() 也支持只读访问。