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

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

本文关键字:获取 tuple 元素 std 编译 不知道      更新时间:2023-10-16

我有一个类型为std::size_t的变量i和一个类型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个元素吗?如何做到这一点?


如果可能的话,我想在不使用助推的情况下完成这项工作

这是可能的:

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

Videone上的工作示例:示例

您不能。这不是元组的作用。如果您需要对元素进行动态访问,请使用std::array<T,N>,它与std::tuple<T,...,T>几乎相同,但提供了动态[i]运算符;或者甚至是像CCD_ 11这样的完全动态容器。

这可能不是OP想要的,但无论如何,只要返回boost::variantboost::any等变体类型,就可以使用运行时i返回第i个元素,

#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=41=6.62=你好3=7在抛出"std::out_of_range"的实例后调用terminatewhat((:元组元素超出范围。中止

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

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

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

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

该代码"动态"处理元素,给定索引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);

不过,让它完全通用是另一回事。至少它需要独立于元组类型。然后,您可能想要生成函子的返回类型,这样就可以返回有意义的结果。第三,使函子接受额外的参数。

附言:我不是真正的C++开发人员,所以上面的方法可能完全没有意义。然而,我发现它对我的微控制器项目很有用,我希望在编译时尽可能多地解决问题,但又足够通用,这样我就可以轻松地处理事情。例如,我的项目中的"菜单"基本上是一个"操作"的元组,每个操作都是一个单独的类,支持简单的协议,如"在LCD上的当前位置打印标签"answers"激活并运行UI循环"。