当我在编译时不知道时,如何从std :: tuple获取第i个元素?

时间:2011-11-19 13:02:56

标签: c++ c++11 tuples

我有一个i类型的变量std::size_t和一个std::tuple类型的元组。我想得到元组的i元素。我试过这个:

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

但是我得到这个编译错误,说第一个模板参数必须是一个整数常量表达式:

  

错误:类型为“std::size_t”的非类型模板参数(又名“unsigned long”)不是整数常量表达式

是否有可能获得元组的i元素,以及如何做到这一点?


如果可能的话,我想在不使用boost的情况下这样做。

4 个答案:

答案 0 :(得分:21)

这是可能的:

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());

此代码将打印:

  

ABC

在ideone上运行示例:sample

答案 1 :(得分:16)

你做不到。这不是一个元组的用途。如果您需要动态访问元素,请使用std::array<T,N>,这与std::tuple<T,...,T>几乎完全相同,但会为您提供动态[i] - 运算符;甚至是像std::vector<T>这样的完全动态的容器。

答案 2 :(得分:13)

这可能不是OP想要的,但无论如何,如果返回变量类型,可以使用运行时 i 返回 i -th元素例如boost::variantboost::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);
}

例如:

#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;
}

将打印:

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

boost::variant<T...>需要g ++ 4.7)

答案 3 :(得分:2)

这里的问题是,如果有可能,那么返回类型是什么?它必须在编译时知道,但元组可能包含不同类型的元素。

假设我们有三个元素的元组:

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

显然,获得第N个元素并没有多大意义。它是什么类型的?直到运行时才知道它。但是,假设所有元素都支持一些通用协议,而不是获取第N个元素,您可以向其应用函数:

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));
}

此代码&#34;动态&#34;在给定索引n的情况下处理元素。此示例中的通用协议是函数func,它可以对元组中使用的所有可能类型执行有意义的操作。

然而,手工编写这样的代码是单调乏味的,我们希望使它更通用。让我们从提取应用程序函数开始,这样我们就可以为不同的仿函数重用相同的process函数:

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));
  }
}

在这种情况下,F可以实现为:

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

让编译器生成所有代码,让它变得通用:

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

用法:

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

让它完全通用是另一个故事。至少它需要独立于元组类型。然后,您可能想要生成仿函数的返回类型,因此您可以返回有意义的结果。第三,让functor接受额外的参数。

P.S。我不是真正的C ++开发人员,所以上面的方法可能完全不存在。但是,我发现它对我的微控制器项目非常有用,我希望尽可能在编译时解决这个问题但又足够通用,所以我可以很容易地改变它。例如,&#34;菜单&#34;在我的项目中基本上是一个&#34;动作&#34;元组,每个动作都是一个单独的类,它支持简单的协议,例如&#34;在LCD上的当前位置打印你的标签&#34;和&#34;激活并运行你的UI循环&#34;。