【问题标题】:In C++, is it possible to get the type of one element of a tuple when the element index is known at runtime?在 C++ 中,当元素索引在运行时已知时,是否可以获得元组的一个元素的类型?
【发布时间】:2026-01-24 11:35:02
【问题描述】:
typedef std::tuple< int, double > Tuple;
Tuple t;
int a = std::get<0>(t);
double b = std::get<1>(t);

for( size_t i = 0; i < std::tuple_size<Tuple>::value; i++ ) {
   std::tuple_element<i,Tuple>::type v = std::get<i>(t);// will not compile because i must be known at compile time
}

我知道可以编写代码让 std::get 工作(例如参见 iterate over tuple ),是否也可以让 std::tuple_element 工作?

一些限制(可以放宽):

没有可变参数模板,没有 Boost

【问题讨论】:

  • 我不明白:为std::tuple 设置编译时索引的重点是类型安全。查询其索引在运行时已知的元素意味着元组的所有成员都具有相同的类型。在这种情况下,您将使用一个数组(最好是std::array)。
  • 你为什么说没有可变参数模板std::tuple可变参数模板。
  • @BjörnPollex 也许我有一个半生不熟的元组 ;-) 截至 2011 年 11 月,Microsoft Visual Studio 2010 中的编译器没有实现可变参数模板。 connect.microsoft.com/VisualStudio/feedback/details/463677/…
  • 问题毫无意义,因为你不知道它的类型,所以你不能对 v 做很多事情。

标签: c++ metaprogramming c++11


【解决方案1】:

C++ 是一种编译时类型的语言。您不能拥有 C++ 编译器在编译时无法确定的类型。

您可以使用各种形式的多态性来解决这个问题。但归根结底,每个变量都必须具有明确定义的类型。因此,虽然您可以使用 Boost.Fusion 算法来迭代元组中的变量,但您不能有一个循环,其中每次执行的循环都可能使用与上一次不同的类型。

Boost.Fusion 能够摆脱它的唯一原因是它不使用循环。它使用模板递归来“迭代”每个元素并调用用户提供的函数。

【讨论】:

    【解决方案2】:

    如果您不想使用 boost,iterate over tuple 的答案已经告诉您您需要知道的一切。您必须编写一个编译时for_each 循环(未经测试)。

    template<class Tuple, class Func, size_t i>
    void foreach(Tuple& t, Func fn) {
        // i is defined at compile-time, so you can write:
        std::tuple_element<i, Tuple> te = std::get<i>(t);
        fn(te);
        foreach<i-1>(t, fn);
    }
    
    template<class Tuple, class Func>
    void foreach<0>(Tuple& t, Func fn) { // template specialization
        fn(std::get<0>(t)); // no further recursion
    }
    

    并像这样使用它:

    struct SomeFunctionObject {
        void operator()( int i ) const {}
        void operator()( double f ) const {}
    };
    
    foreach<std::tuple_size<Tuple>::value>(t, SomeFunctionObject());
    

    但是,如果您想遍历元组的成员,Boost.Fusion 确实是要走的路。

    #include <boost/fusion/algorithm/iteration/for_each.hpp>
    #include <boost/fusion/adapted/boost_tuple.hpp>
    

    在你的代码中写:

    boost::for_each(t, SomeFunctionObject());
    

    这是 boost::tuple 的示例。这里有一个 boost::fusion 适配器可以与 std::tuple 一起使用:http://groups.google.com/group/boost-list/browse_thread/thread/77622e41af1366af/

    【讨论】:

    • 也用于元素的type
    • @uvts_cvs:你会用这种类型做什么?
    • @uvts_cvs 我认为我在 foreach 中的评论清楚地表明您可以在编译时访问该类型。无论如何,我更改了代码以使其明确。
    【解决方案3】:

    不,这不可能像你描述的那样。基本上,您必须为i 的每个可能的运行时值编写代码,然后使用一些调度逻辑(例如switch(i))根据i 的实际运行时值运行正确的代码。

    在实践中,可能可以使用模板为i 的不同值生成代码,但我不确定如何执行此操作,以及它是否实用。你所描述的听起来像是一个有缺陷的设计。

    【讨论】:

      【解决方案4】:

      这是我的元组 foreach/transformation 函数:

      #include <cstddef>
      #include <tuple>
      #include <type_traits>
      
      template<size_t N>
      struct tuple_foreach_impl {
          template<typename T, typename C>
          static inline auto call(T&& t, C&& c)
              -> decltype(::std::tuple_cat(
                  tuple_foreach_impl<N-1>::call(
                      ::std::forward<T>(t), ::std::forward<C>(c)
                  ),
                  ::std::make_tuple(c(::std::get<N-1>(::std::forward<T>(t))))
              ))
          {
              return ::std::tuple_cat(
                  tuple_foreach_impl<N-1>::call(
                      ::std::forward<T>(t), ::std::forward<C>(c)
                  ),
                  ::std::make_tuple(c(::std::get<N-1>(::std::forward<T>(t))))
              );
          }
      };
      
      template<>
      struct tuple_foreach_impl<0> {
          template<typename T, typename C>
          static inline ::std::tuple<> call(T&&, C&&) { return ::std::tuple<>(); }
      };
      
      template<typename T, typename C>
      auto tuple_foreach(T&& t, C&& c)
          -> decltype(tuple_foreach_impl<
              ::std::tuple_size<typename ::std::decay<T>::type
          >::value>::call(std::forward<T>(t), ::std::forward<C>(c)))
      {
          return tuple_foreach_impl<
              ::std::tuple_size<typename ::std::decay<T>::type>::value
          >::call(::std::forward<T>(t), ::std::forward<C>(c));
      }
      

      示例用法使用以下实用程序允许将元组打印到 ostream:

      #include <cstddef>
      #include <ostream>
      #include <tuple>
      #include <type_traits>
      
      template<size_t N>
      struct tuple_print_impl {
          template<typename S, typename T>
          static inline void print(S& s, T&& t) {
              tuple_print_impl<N-1>::print(s, ::std::forward<T>(t));
              if (N > 1) { s << ',' << ' '; }
              s << ::std::get<N-1>(::std::forward<T>(t));
          }
      };
      
      template<>
      struct tuple_print_impl<0> {
          template<typename S, typename T>
          static inline void print(S&, T&&) {}
      };
      
      template<typename S, typename T>
      void tuple_print(S& s, T&& t) {
          s << '(';
          tuple_print_impl<
              ::std::tuple_size<typename ::std::decay<T>::type>::value
          >::print(s, ::std::forward<T>(t));
          s << ')';
      }
      
      template<typename C, typename... T>
      ::std::basic_ostream<C>& operator<<(
          ::std::basic_ostream<C>& s, ::std::tuple<T...> const& t
      ) {
          tuple_print(s, t);
          return s;
      }
      

      最后,这里是示例用法:

      #include <iostream>
      
      using namespace std;
      
      struct inc {
          template<typename T>
          T operator()(T const& val) { return val+1; }
      };
      
      int main() {
          // will print out "(7, 4.2, z)"
          cout << tuple_foreach(make_tuple(6, 3.2, 'y'), inc()) << endl;
          return 0;
      }
      

      请注意,可调用对象的构造是为了在需要时保持状态。例如,您可以使用以下内容查找元组中可以动态转换为 T 的最后一个对象:

      template<typename T>
      struct find_by_type {
          find() : result(nullptr) {}
          T* result;
          template<typename U>
          bool operator()(U& val) {
              auto tmp = dynamic_cast<T*>(&val);
              auto ret = tmp != nullptr;
              if (ret) { result = tmp; }
              return ret;
          }
      };
      

      请注意,这样做的一个缺点是它要求可调用对象返回一个值。但是,重写它以检测给定输入类型的返回类型是否为 void,然后跳过结果元组的那个元素并不难。更简单的是,您可以完全删除返回值聚合的东西,只需将 foreach 调用用作元组修饰符。

      编辑: 我刚刚意识到可以使用 foreach 函数轻松编写元组编写器(我的元组打印代码比 foreach 代码要长得多)。

      template<typename T>
      struct tuple_print {
          print(T& s) : _first(true), _s(&s) {}
          template<typename U>
          bool operator()(U const& val) {
              if (_first) { _first = false; } else { (*_s) << ',' << ' '; }
              (*_s) << val;
              return false;
          }
      private:
          bool _first;
          T* _s;
      };
      
      template<typename C, typename... T>
      ::std::basic_ostream<C> & operator<<(
          ::std::basic_ostream<C>& s, ::std::tuple<T...> const& t
      ) {
          s << '(';
          tuple_foreach(t, tuple_print< ::std::basic_ostream<C>>(s));
          s << ')';
          return s;
      }
      

      【讨论】:

        最近更新 更多