【问题标题】:C++0x: iterating through a tuple with a functionC ++ 0x:使用函数迭代元组
【发布时间】:2010-07-23 08:36:52
【问题描述】:

我有一个名为_push 的函数,它可以处理不同的参数,包括元组,并且应该返回推送元素的数量。

例如,_push(5) 应将“5”压入堆栈(stack of lua)并返回 1(因为压入了一个值),而 _push(std::make_tuple(5, "hello")) 应压入“5”和“hello”并返回 2 .

我不能简单地用_push(5, "hello") 替换它,因为我有时会使用_push(foo()),我想让foo() 返回一个元组。

无论如何我无法让它与元组一起工作:

template<typename... Args, int N = sizeof...(Args)>
int _push(const std::tuple<Args...>& t, typename std::enable_if<(N >= 1)>::type* = nullptr) {
 return _push<Args...,N-1>(t) + _push(std::get<N-1>(t));
}

template<typename... Args, int N = sizeof...(Args)>
int _push(const std::tuple<Args...>& t, typename std::enable_if<(N == 0)>::type* = nullptr) {
 return 0;
}

假设您要推送tuple&lt;int,bool&gt;。这就是我期望它的工作方式:

  • _push&lt;{int,bool}, 2&gt; 被调用(第一个定义)
  • _push&lt;{int,bool}, 1&gt; 被调用(第一个定义)
  • _push&lt;{int,bool}, 0&gt; 被调用(第二个定义)

但是使用 g++ 4.5(我拥有的唯一支持可变参数模板的编译器),我收到一个关于 _push&lt;Args...,N-1&gt;(t)(第 3 行)的错误,说它找不到要调用的匹配函数(没有任何进一步的细节)。我试过不带“...”,但我收到另一个错误,说参数包没有展开。

我该如何解决这个问题?

PS:我知道你可以使用模板结构来做到这一点(这实际上是我之前所做的),但我想知道如何使用函数来做到这一点

PS2:PS2 已解决,感谢 GMan

【问题讨论】:

  • 我对你的目标感到困惑。无论如何,对于 PS2,您需要 sizeof...(Args),它返回包中的参数数量。
  • 谢谢;我的目标很简单:我想将元组中的所有元素一个一个地推送,而不必为此专门创建一个结构;我认为可以像我在我的问题中那样做,但 g++ 不这么认为
  • 我听不懂你的动词“push”。介意假装这些函数工作,然后给出一些输入和输出的例子吗?
  • 事实上这是一个围绕 lua 的包装器(具有高级特性),“push”应该将一个值压入 lua 的堆栈; push(5) 或 push("hello") 应该返回 1,push(std::make_tuple(5, "hello")) 应该返回 2
  • @tomaka:无论如何要把那种东西存进去?返回值有什么意义?恐怕没有例子,我至少无法很好地掌握“推”。 (不过我对 Lua 很熟悉。)你的目标是什么? (形式为:“致电push(...),让___发生。”

标签: c++ c++11 tuples


【解决方案1】:

我没有编译器来测试这些,所以你必须报告任何问题。

以下内容应该允许您遍历调用函数的元组。它基于您的逻辑,并进行了一些小的更改。 (N 是一个std::size_t,它是第一个允许在进一步调用时推断出Args(和Func)的参数,它只是调用一些函数而不是执行特定任务)。没有什么太激烈的:

namespace detail
{
    // just to keep things concise and readable
    #define ENABLE_IF(x) typename std::enable_if<(x)>::type

    // recursive case
    template <std::size_t N, typename... Args, typename Func>
    ENABLE_IF(N >= 1) iterate(const std::tuple<Args...>& pTuple, Func& pFunc)
    {
        pFunc(std::get<N - 1>(pTuple));

        iterate<N - 1>(pTuple, pFunc);
    }

    // base case
    template <std::size_t N, typename... Args, typename Func>
    ENABLE_IF(N == 0) iterate(const std::tuple<Args...>&, Func&)
    {
        // done
    }
}

// iterate tuple
template <typename... Args, typename Func>
Func iterate(const std::tuple<Args...>& pTuple, Func pFunc)
{
    detail::iterate<sizeof...(Args)>(pTuple, pFunc);

    return pFunc;
}

假设一切正常,那么您只需:

struct push_lua_stack
{
    // constructor taking reference to stack to push onto
    // initialize count to 0, etc....

    template <typename T>
    void operator()(const T& pX)
    {
        // push pX onto lua stack
        ++count;
    }

    std::size_t count;
};

最后:

std::size_t pushCount = iterate(someTuple, push_lua_stack()).count;

让我知道这一切是否有意义。


由于某种原因,您似乎真的非常反对结构,所以只需创建一个这样的函数:

template <typename T>
void push_lua(const T& pX)
{
    // push pX onto lua stack
}

并更改所有内容以专门调用该函数:

namespace detail
{
    // just to keep things concise and readable
    #define ENABLE_IF(x) std::enable_if<(x)>::type* = nullptr

    // recursive case
    template <std::size_t N, typename... Args>
    typename ENABLE_IF(N >= 1) iterate(const std::tuple<Args...>& pTuple)
    {
        // specific function instead of generic function
        push_lua(std::get<N - 1>(pTuple));

        iterate<N - 1>(pTuple);
    }

    // base case
    template <std::size_t N, typename... Args, typename Func>
    typename ENABLE_IF(N == 0) iterate(const std::tuple<Args...>&, Func&)
    {
        // done
    }
}

// iterate tuple
template <typename... Args>
void _push(const std::tuple<Args...>& pTuple)
{
    detail::iterate<sizeof...(Args)>(pTuple);
}

不知道为什么要避免通用功能,或者如此反对结构。


哦,多态 lambda 是多么美妙啊。放弃实用程序push_lua_stack 类,只需编写:

std::size_t count = 0;

iterate(someTuple, [&](auto pX)
                    {
                        // push onto lua stack
                        ++count;
                    });

哦,好吧。

【讨论】:

  • 别误会,我只是想避免模板结构
  • @Tomaka:该结构只是将一些T 推送到 lua 堆栈上。如果你理解代码,你可以看到你可以用一些特定的函数替换对pFunc 的函数调用(也就是删除结构)。已添加。
【解决方案2】:

我用一些技巧解决了这个问题。代码如下:

template<typename... Args, int N = sizeof...(Args)>
int _push(const std::tuple<Args...>& t, std::integral_constant<int,N>* = nullptr, typename std::enable_if<(N >= 1)>::type* = nullptr) {
    return _push(t, static_cast<std::integral_constant<int,N-1>*>(nullptr)) + _push(std::get<N-1>(t));
}
template<typename... Args, int N = sizeof...(Args)>
int _push(const std::tuple<Args...>& t, std::integral_constant<int,N>* = nullptr, typename std::enable_if<(N == 0)>::type* = nullptr) {
    return 0;
}

如果您找到更好的方法,请不要犹豫

【讨论】:

    【解决方案3】:

    不要让一个函数做两件不同的事情,而是将关注点分开:

    _push(value, ...); // using variadic templates for 1 to N values
    _push_seq(sequence); // always requires a sequence, never a value
    

    那么 _push 的问题就不再存在了!对于是推送一个包含多个值的项目(我不熟悉 Lua,但我知道它有一个主容器类)还是从一个序列推送多个项目,您没有歧义。

    重命名函数可能会有所帮助:

    _append(value, ...); // _push(value, ...) above
    _extend(sequence); // _push(sequence) above
    

    为了比较,考虑一下 std::vector 如何总是对一个项目使用 push_back (_append) 并为多个项目使用 insert (_extend);它不会试图混合这两个概念。

    【讨论】:

      【解决方案4】:

      如果您想使用函数迭代元组,您可以使用(少量)样板代码来实现。这个想法是建立一个对应于元组索引的可变整数列表,然后使用 std::get 来访问值。快点:

      template<int...> struct indices;
      
      // constructs an index list for a tuple e.g. indices<0, 1, 2, 3> 
      template<class ... Args> some_index_type make_indices();
      

      然后你可以像这样展开一个元组:

      template<class Args...> void foo(const std::tuple<Args...>& tup) {
          foo(tup, make_indices<Args...>());
      }
      
      template<class Args..., int...I> void foo(const std::tuple<Args...>& tup,
                                                  indices<I...> ){
         bar( std::get<I>(tup)... );
      }
      

      这将扩展元组内容并将其提供给功能栏。

      希望这会有所帮助:)

      【讨论】:

        【解决方案5】:

        这是我能想到的最简单的解决方案之一。我用 GCC 4.4 成功测试了它:

        #include <iostream>
        #include <tuple>
        
        template<class T>
        void push(T x)
        {
          using namespace std;
          cout << x << '\n';
        }
        
        template<int Remaining>
        struct push_tuple_helper
        {
          template<class... Args>
          static void doit(std::tuple<Args...> const& t)
          {
            push(std::get<sizeof...(Args)-Remaining>(t));
            push_tuple_helper<Remaining-1>::doit(t);
          }
        };
        
        template<>
        struct push_tuple_helper<0>
        {
          template<class... Args>
          static void doit(std::tuple<Args...> const& t) {}
        };
        
        template<class... Args>
        void push(std::tuple<Args...> t)
        {
          push_tuple_helper<sizeof...(Args)>::doit(t);
        }
        
        int main()
        {
          using namespace std;
          push( 42 );
          cout << "---\n";
          push( "Hello World" );
          cout << "---\n";
          push( make_tuple(42,3.14,"foo") );
        }
        

        【讨论】:

          【解决方案6】:

          为每个编辑通用

          template<size_t N>
          struct for_each_impl
          {
            template<typename Func, typename Tuple>
            void operator()(Func func, Tuple const& arg)
            {
              for_each_impl<N-1>()(func, arg );
              return func( std::get<N-1>( arg ) );
            }
          };
          
          template<>
          struct for_each_impl<1>
          {
            template<typename Func, typename Tuple>
            void operator()(Func func, Tuple const& arg)
            {
              func( std::get<0>( arg ) );
            }
          };
          
          template<typename Func, typename ... Args>
          void for_each( Func func, std::tuple<Args...>const& tup )
          {
            for_each_impl< sizeof...(Args)>()( func, tup );
          }
          

          示例用法

          struct printer {
              ostream& out;
              explicit printer( ostream& out=std::cout ) : out(out) { }
          
              template<typename T>void operator()(T const&t) const { out<<t<<", "; }
          };
          
          cout << '[';
          for_each( printer(cout), make_tuple(0,.1,"hello") );
          cout << ']';
          

          【讨论】:

          • @osgx 我完全不记得回答过这个问题,所以我不能告诉你。上面的代码显示了如何对元组中的每个值执行任意操作。因此,很容易适应 OP 遇到的任何问题。我猜他只是讨厌 MPL
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-03-30
          • 2019-02-17
          • 1970-01-01
          • 1970-01-01
          • 2014-04-22
          相关资源
          最近更新 更多