How do I use a variable to index into a tuple using std::get<>? I have the following code:
#include
#include
using n
#include <utility>
template<std::size_t...Is>
auto index_over( std::index_sequence<Is...> ) {
return [](auto&& f)->decltype(auto){
return decltype(f)(f)( std::integral_constant<std::size_t, Is>{}... );
};
}
template<std::size_t N>
auto index_upto( std::integral_constant<std::size_t, N> ={} ) {
return index_over( std::make_index_sequence<N>{} );
}
template<class F>
auto foreacher( F&& f ) {
return [f=std::forward<F>(f)](auto&&...args)mutable {
(void(), ..., void(f(decltype(args)(args))));
};
}
template<std::size_t N>
auto count_upto( std::integral_constant<std::size_t, N> ={} ) {
return [](auto&& f){
index_upto<N>()(foreacher(decltype(f)(f)));
};
}
you can just do:
#include <iostream>
#include <tuple>
int main() {
std::tuple<int, int> data(5, 10);
count_upto<2>()([&](auto I){
std::cout << "#" << (I+1) << ":" << std::get<I>(data) << "\n";
});
}
Live example.
There is no unbounded recursion in this solution. It does require C++1z -- you can replace the body of foreacher
with the using unused=int[];
trick in C++14.
Any idea how to make that work?
Use compile time constants to access the std::tuple
.
cout << "#" << 1 << ":" << get<0>(data) << endl;
cout << "#" << 2 << ":" << get<1>(data) << endl;
Use a container type whose elements can be accessed using an index at run time.
std::vector<int> data{5, 10};
or
std::array<int, 2> data{5, 10};
How do I use a variable to index into a tuple using std::get<>?
You do not, std::get<>
parameter value must be known at compile time.
Any idea how to make that work?
yes, use proper type:
int main() {
std::array<int, 2> data{ 5, 10 };
for (int i=0; i<2; i++) {
cout << "#" << i+1 << ":" << data[i] << endl;
}
return 0;
}
The likely answer that you should adopt is to just use an array, vector, or other kind of indexed container.
In the event that the tuple elements are not homogeneous types and you actually do need an answer for that case, it's a bit complex. This is because types need to be known at compile time. So where you think you could do std::cout << get_from_tuple(a_tuple, index)
for example, this can't work as easily as you think it can because the operator<<
overload for sending the object to the standard output stream is selected at compile time. Obviously, this means that the index must be known at compile time, too -- otherwise we can't know the type of the tuple element.
However, it is possible to build a template function that can, in fact, implement exactly this behavior. The end result, when compiled, is a conditional tree that is capable of handling each element in the tuple, but we enlist the compiler to help us build that conditional tree.
What I will build here is a function that, given a tuple, index, and functor, will invoke the functor, forwarding that particular tuple item, and will then return true. If the index is out of range, it will return false.
If the functor is not able to be called with every element in the tuple, the template function will fail to instantiate.
The final solution looks like this:
#include <tuple>
#include <type_traits>
namespace detail {
template <std::size_t I>
struct size_wrapper { };
template <typename V, typename Tup, std::size_t I>
bool visit_tuple_index_impl(Tup && t, std::size_t index, V && visitor, size_wrapper<I>)
{
if (index == I - 1) {
visitor(std::get<I - 1>(std::forward<Tup>(t)));
return true;
}
return visit_tuple_index_impl(std::forward<Tup>(t), index, visitor, size_wrapper<I - 1>());
}
template <typename V, typename Tup>
bool visit_tuple_index_impl(Tup &&, std::size_t, V &&, size_wrapper<0>)
{
return false;
}
}
template <typename V, typename Tup>
bool visit_tuple_index(Tup && t, std::size_t index, V && visitor)
{
return detail::visit_tuple_index_impl(
std::forward<Tup>(t),
index,
std::forward<V>(visitor),
detail::size_wrapper<std::tuple_size<typename std::decay<Tup>::type>::value>()
);
}