22

I have a variable i of type std::size_t and a tuple of type std::tuple. I want to get the i-th element of the tuple. I tried this:

// bindings... is of type const T&...
auto bindings_tuple = std::make_tuple(bindings...);
auto binding = std::tuple_element<i, const T&...>(bindings_tuple);

But I get this compile error saying that the first template argument must be an integral constant expression:

error: non-type template argument of type 'std::size_t' (aka 'unsigned long') is not an integral constant expression

Is it possible to get the i-th element of a tuple, and how to do that?


I would like to do this without using boost, if possible.

  • I think that this is a strong indication that you need to use a vector, not a tuple. – Jordão Nov 19 '11 at 13:07
  • 3
    @Jordão the problem with vectors (and also arrays) is that all elements need to be of the same type. –  Nov 19 '11 at 13:09
  • Unless it's a vector of objects? – N_A Nov 19 '11 at 13:12
  • 2
    You need to know the type at compile-time. You either know the index at compile-time, or have all elements of the same type. There's no way around it. – n. m. could be an AI Nov 19 '11 at 13:14
  • 1
    There is something wrong when the situation arises. A tuples type has to be known at compile time, how can you not know what type to access at compile time? You wouldn't even be able to write what that function is supposed to return. – pmr Nov 19 '11 at 13:38
  • @WTP: It doesn't make sense to have different types selected at runtime. If you have `tuple x;`, you can say `char & y = std::get<0>(x);`, but what type would you put if the index wasn't known? – Kerrek SB Nov 19 '11 at 13:38
  • @Kerrek: Maybe `decltype(1 ? char() : 1 ? bool() : double())`? (i.e. `double` in this case). – Steve Jessop Nov 19 '11 at 16:37
  • @SteveJessop: That looks like Baby's First `std::common_type`! – Kerrek SB Nov 19 '11 at 16:38
  • @Kerrek: ah, didn't know about that. Yes, `common_type::type` then. Whether the program can actually make sense of the value is left as an exercise for the reader... – Steve Jessop Nov 19 '11 at 16:40
  • @SteveJessop: More importantly, why wouldn't you just replace the element types by the common type and be done with it? Looks like there's nothing you can get out of this information which is immediately converted away. – Kerrek SB Nov 19 '11 at 16:53
  • @Kerrek: Well, `double` might make this a special case in that (on almost any implementation) it can store all values of the other two types. So an `array` would indeed do the job. I've no idea how weird the questioner's code is, or exactly how to improve it, but conceivably this bit of code that wants to access by index doesn't care about the different types, whereas other bits of code do respect them. Equally conceivably, there is no common type and the questioner is out of luck. – Steve Jessop Nov 19 '11 at 17:00

4 Answers4

33

This is possible:

struct Functor 
{
    template<typename T>
    void operator()(T& t) const { std::cout << t << std::endl; }
};

template<std::size_t I = 0, typename FuncT, typename... Tp>
inline typename std::enable_if<I == sizeof...(Tp), void>::type
  for_index(int, std::tuple<Tp...> &, FuncT)
  { }

template<std::size_t I = 0, typename FuncT, typename... Tp>
inline typename std::enable_if<I < sizeof...(Tp), void>::type
  for_index(int index, std::tuple<Tp...>& t, FuncT f)
  {
    if (index == 0) f(std::get<I>(t));
    for_index<I + 1, FuncT, Tp...>(index-1, t, f);
  }

auto t = make_tuple(1, 2, "abc", "def", 4.0f);
int i = 2; // for example
for_index(i, t, Functor());

This code will print:

abc

Working sample on ideone: sample

Victor Laskin
  • 2,577
  • 2
  • 16
  • 10
17

You cannot. That's not what a tuple is for. If you need dynamic access to an element, use std::array<T,N>, which is almost identical to std::tuple<T,...,T> but gives you the dynamic [i]-operator; or even a fully dynamic container like std::vector<T>.

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • 1
    To be more precise, I think, he needs `std::array` or `std::vector`. – Nawaz Nov 19 '11 at 13:17
  • 2
    @Nawaz: Possibly, though the question confuses "template" and "type", so there are limits to what one can say. Ultimately, it doesn't really make sense to have non-compile-time switching between *different* types, at least not in an elegant fashion... – Kerrek SB Nov 19 '11 at 13:21
  • boost::varient allows it, and I imagine you'd do a similar thing for tuples. Basically you have a function that takes a tuple, an index into the tuple, and a class with operator() overloads for each type. The appropriate overload would be called with the value at the given index of the tuple. – bames53 Nov 20 '11 at 00:48
  • @bames53: As I said, "not in an elegant fashion" :-) (I'm half kidding. If this is really the best design for your problem, go with it.) – Kerrek SB Nov 20 '11 at 00:53
  • This is blatantly false. See the other answer, this question https://stackoverflow.com/questions/28997271/c11-way-to-index-tuple-at-runtime-without-using-switch or this blog post https://www.justsoftwaresolutions.co.uk/cplusplus/getting-tuple-elements-with-runtime-index.html – scx Nov 02 '19 at 17:21
  • @scx: That answer seems to be constrained to particular kinds of tuples (e.g. those with all equal, or at least all convertible, types)? – Kerrek SB Nov 03 '19 at 01:32
  • @KerrekSB No, it works with any type at runtime. By using lambdas with auto parameters, you can access whichever type is at the provided index. [](auto& obj){}. You can see my answer at the bottom there for an even simpler implementation in c++17. Cheers – scx Nov 03 '19 at 17:15
  • @scx: This is a C++11 question, though, isn't it? :-) – Kerrek SB Nov 03 '19 at 21:28
13

This is probably not what OP wants, but anyway, it is possible to return the i-th element using a run-time i provided you return a variant type such as boost::variant or boost::any,

#include <tuple>
#include <stdexcept>
#include <boost/variant.hpp>

template <size_t n, typename... T>
boost::variant<T...> dynamic_get_impl(size_t i, const std::tuple<T...>& tpl)
{
    if (i == n)
        return std::get<n>(tpl);
    else if (n == sizeof...(T) - 1)
        throw std::out_of_range("Tuple element out of range.");
    else
        return dynamic_get_impl<(n < sizeof...(T)-1 ? n+1 : 0)>(i, tpl);
}

template <typename... T>
boost::variant<T...> dynamic_get(size_t i, const std::tuple<T...>& tpl)
{
    return dynamic_get_impl<0>(i, tpl);
}

For example:

#include <string>
#include <iostream>

int main()
{
    std::tuple<int, float, std::string, int> tpl {4, 6.6, "hello", 7};

    for (size_t i = 0; i < 5; ++ i)
        std::cout << i << " = " << dynamic_get(i, tpl) << std::endl;

    return 0;
}

will print:

0 = 4
1 = 6.6
2 = hello
3 = 7
terminate called after throwing an instance of 'std::out_of_range'
  what():  Tuple element out of range.
Aborted

(The boost::variant<T...> requires g++ 4.7)

kennytm
  • 510,854
  • 105
  • 1,084
  • 1,005
3

The question here, what would be the type return type if that would be possible? It has to be known at compile time, but tuple may contain elements of different types.

Let's assume we have a tuple of three elements:

auto tuple = std::make_tuple(10, "", A());
using tuple_type = decltype(tuple);

Apparently, getting N-th element doesn't make much sense. What type would it be? It's not known until runtime. However, rather than getting N-th element you can apply a function to it, given that all elements support some common protocol:

void process(int n)
{
  if (n == 0)
    func(std::get<0>(tuple));
  else if (n == 1)
    func(std::get<1>(tuple));
  else if (n == 2)
    func(std::get<2>(tuple));
}

This code "dynamically" processes element, given the index n. The common protocol in this example is function func which can do something meaningful with all possible types used in the tuple.

However, writing such code by hand is tedious, we want to make it more generic. Let's start with extracting the application function, so we can reuse same process function for different functors:

template<template<typename > class F>
void process(int n)
{
  if (n == 0)
  {
    using E = typename std::tuple_element<0, tuple_type>::type;
    F<E>::apply(std::get<0>(tuple));
  }
  else if (n == 1)
  {
    using E = typename std::tuple_element<1, tuple_type>::type;
    F<E>::apply(std::get<1>(tuple));
  }
  else if (n == 2)
  {
    using E = typename std::tuple_element<2, tuple_type>::type;
    F<E>::apply(std::get<2>(tuple));
  }
}

In this case F could be implemented as something like:

// Prints any printable type to the stdout
struct printer
{
  static void apply(E e)
  {
    std::cout << e << std::endl;
  }
}

Let's make compiler to generate all of that code, let's make it generic:

constexpr static std::size_t arity = std::tuple_size<tuple_type>::value;
template<int N>
struct wrapper
{
  template<template<typename, typename ... > class F>
  static void apply_to(tuple_type& tuple, int idx)
  {
    if (idx)
      // Double recursion: compile and runtime.
      // Compile-time "recursion" will be terminated once
      // we reach condition N == tuple arity
      // Runtime recursion terminates once idx is zero.
      wrapper<N + 1>::template apply_to<F>(tuple, idx - 1);
    else
    {
      // idx == 0 (which means original index is equal to N).
      using E = typename std::tuple_element<N, tuple_type>::type;
      F<E>::apply(std::get<N>(tuple));
    }
  }
};

// Termination condition: N == arity.
template<>
struct wrapper<arity>
{
  template<template<typename, typename ... > class F>
  static void apply_to(tuple_type&, int)
  {
    // Throw exception or something. Index is too big.
  }
};

Usage:

wrapper<0>::template apply_to<printer>(tuple, 2);

Making it completely generic is another story, though. At least it needs to be independent of the tuple type. Then, you probably want to generify return type of the functor, so you can return meaningful result. Third, making functor to accept extra parameters.

P.S. I am not real C++ developer, so the approach above could be total nonsence. However, I found it useful for my microcontroller project where I want as much as possible to be resolved at compile time and yet be generic enough, so I can shuffle things around easily. For example, a "menu" in my project is basically a tuple of "actions", there each action is a separate class which supports simple protocol like "print your label at current position on LCD" and "activate and run your UI loop".

Ivan Dubrov
  • 4,778
  • 2
  • 29
  • 41