7

How do I use a variable to index into a tuple using std::get<>? I have the following code:

#include <iostream>
#include <tuple>
using namespace std;

int main() {
  tuple<int, int> data(5, 10);
  for (int i=0; i<2; i++) {
    cout << "#" << i+1 << ":" << get<i>(data) << endl;
  }
  return 0;
}

and it fails with the following compiler error:

prog.cpp: In function 'int main()':
prog.cpp:10:39: error: the value of 'i' is not usable in a constant expression
      cout << "#" << i+1 << ":" << get<i>(data) << endl;
                                       ^
prog.cpp:9:11: note: 'int i' is not const
  for (int i=0; i<2; i++) {
           ^
prog.cpp:10:46: error: no matching function for call to 
    'get(std::tuple<int, int>&)'
          cout << "#" << i+1 << ":" << get<i>(data) << endl;
                                          ^
prog.cpp:10:46: note: candidates are:
In file included from /usr/include/c++/4.9/tuple:38:0,
                 from prog.cpp:2:
/usr/include/c++/4.9/utility:143:5: note: template<unsigned int _Int, 
class _Tp1, class _Tp2> constexpr typename std::tuple_element<_Int, 
std::pair<_Tp1, _Tp2> >::type& std::get(std::pair<_Tp1, _Tp2>&)
     get(std::pair<_Tp1, _Tp2>& __in) noexcept
     ^
/usr/include/c++/4.9/utility:143:5: note:   template argument 
deduction/substitution failed:
prog.cpp:10:46: error: the value of 'i' is not usable in a constant 
expression
      cout << "#" << i+1 << ":" << get<i>(data) << endl;
                                              ^
prog.cpp:9:11: note: 'int i' is not const
  for (int i=0; i<2; i++) {
           ^
prog.cpp:10:46: note: in template argument for type 'unsigned int' 
      cout << "#" << i+1 << ":" << get<i>(data) << endl;
                                              ^
In file included from /usr/include/c++/4.9/tuple:38:0,
                 from prog.cpp:2:
/usr/include/c++/4.9/utility:148:5: note: template<unsigned int _Int, 
class _Tp1, class _Tp2> constexpr typename std::tuple_element<_Int, 
std::pair<_Tp1, _Tp2> >::type&& std::get(std::pair<_Tp1, _Tp2>&&)
     get(std::pair<_Tp1, _Tp2>&& __in) noexcept
     ^
/usr/include/c++/4.9/utility:148:5: note:   template argument 
deduction/substitution failed:
prog.cpp:10:46: error: the value of 'i' is not usable in a constant 
expression
      cout << "#" << i+1 << ":" << get<i>(data) << endl;
                                              ^
prog.cpp:9:11: note: 'int i' is not const
  for (int i=0; i<2; i++) {
           ^
prog.cpp:10:46: note: in template argument for type 'unsigned int' 
      cout << "#" << i+1 << ":" << get<i>(data) << endl;
                                              ^
In file included from /usr/include/c++/4.9/tuple:38:0,
                 from prog.cpp:2:
/usr/include/c++/4.9/utility:153:5: note: template<unsigned int _Int, 
class _Tp1, class _Tp2> constexpr const typename 
std::tuple_element<_Int, std::pair<_Tp1, _Tp2> >::type& std::get(const 
std::pair<_Tp1, _Tp2>&)
     get(const std::pair<_Tp1, _Tp2>& __in) noexcept
     ^
/usr/include/c++/4.9/utility:153:5: note:   template argument 
deduction/substitution failed:
prog.cpp:10:46: error: the value of 'i' is not usable in a constant 
expression
      cout << "#" << i+1 << ":" << get<i>(data) << endl;
                                              ^
prog.cpp:9:11: note: 'int i' is not const
  for (int i=0; i<2; i++) {
           ^
prog.cpp:10:46: note: in template argument for type 'unsigned int' 
      cout << "#" << i+1 << ":" << get<i>(data) << endl;
                                              ^
In file included from /usr/include/c++/4.9/tuple:38:0,
                 from prog.cpp:2:
/usr/include/c++/4.9/utility:162:5: note: template<class _Tp, class 
_Up> constexpr _Tp& std::get(std::pair<_T1, _T2>&)
     get(pair<_Tp, _Up>& __p) noexcept

I actually truncated the compiler error message as I think it does not add much beyond thid point. Any idea how to make that work?

Edit:

Just to clarify, using an array type is not really an option. I have to use the tuple cause it is the return type of an API from a third party library. The example above is just to make it easy to understand.

ragnar
  • 71
  • 1
  • 3
  • 3
    The simple answer is that tuple is the wrong tool; you are looking for an array. Note that each element in a tuple can have a different type, which means that the compiler wouldn't even know which `operator<<` overload to use. E.g. if you have a `std::tuple` and you could specify the tuple index by variable, should `std::cout << std::get(data)` write an int or a string? This stuff has to be known during compilation to pick the correct overload. – cdhowie Apr 25 '17 at 21:16
  • 4
    The index to `std::get` is template argument, it has to be known at compile time. – Zereges Apr 25 '17 at 21:16
  • Reopening because the other question is asking a slightly different question, and the answer is not applicable to this one. The questions are related but different enough that I think this one deserves a proper answer. – cdhowie Apr 25 '17 at 21:20
  • Note that you can make a recursive template function that can apply `std::get` using a runtime-supplied value, but unless the `std::tuple` type uses the exact same type for each element, you have to deal with the complexity that you have to be able to handle *any type in the tuple* -- which, of course, could be done. For example, by having the function return a `boost::any` object, or passing the value to a functor/visitor object that has overloads to handle any of the types. – cdhowie Apr 25 '17 at 21:22
  • @cdhowie Okay, assume all elements in the tuple have the same type (which is true in my case), can you please elaborate on your solution? – ragnar Apr 25 '17 at 22:28
  • @user2031282 See my answer -- works for homogeneous and heterogeneous tuples. If the tuple is homogeneous you can just use a lambda `[&] (int v) { /* do something with v */ ]`, for example. – cdhowie Apr 25 '17 at 22:35
  • http://stackoverflow.com/a/37602149/440403 This link answered why get<> failed in for loop – camino May 16 '17 at 18:38

4 Answers4

9

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;
}
Slava
  • 43,454
  • 1
  • 47
  • 90
4

Any idea how to make that work?

Option 1

Use compile time constants to access the std::tuple.

cout << "#" << 1 << ":" << get<0>(data) << endl;
cout << "#" << 2 << ":" << get<1>(data) << endl;

Option 2

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};
R Sahu
  • 204,454
  • 14
  • 159
  • 270
2

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>()
    );
}
cdhowie
  • 158,093
  • 24
  • 286
  • 300
0
#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.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524