How to get the i-th element from an std::tuple when i isn't know at compile-time?

时光毁灭记忆、已成空白 提交于 2019-12-03 05:49:17

问题


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.


回答1:


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>.




回答2:


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




回答3:


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)




回答4:


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".



来源:https://stackoverflow.com/questions/8194227/how-to-get-the-i-th-element-from-an-stdtuple-when-i-isnt-know-at-compile-time

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!