C++0x:使用函数遍历元组

3

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

例如,_push(5)应该将'5'推入堆栈(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<int,bool>。我希望它能够按以下方式工作:
  • 调用_push<{int,bool}, 2>(第一个定义)
  • 调用_push<{int,bool}, 1>(第一个定义)
  • 调用_push<{int,bool}, 0>(第二个定义)
然而,在g++ 4.5中(我唯一支持可变参数模板的编译器),我得到了有关_push<Args...,N-1>(t)(第3行)的错误,称无法找到要调用的匹配函数(没有进一步的细节)。 我尝试过去掉“...”,但是我得到了另一个错误,说参数包未展开。
如何解决这个问题?
PS:我知道您可以使用模板结构来完成此操作(实际上这就是我之前所做的),但我想知道如何使用函数来完成它
PS2:PS2已解决,谢谢GMan

我对你的目标感到困惑。无论如何,对于PS2,你需要使用sizeof...(Args),它返回包中参数的数量。 - GManNickG
谢谢;我的目标很简单:我想逐个推送元组中的所有元素,而不必专门为此创建一个结构;我认为可以通过像我在问题中所做的那样来实现,但g++并不这么认为。 - Tomaka17
实际上,这是围绕Lua的一个包装器(具有高级功能),“push”应该将一个值推送到Lua堆栈上;push(5)或push(“hello”)应返回1,push(std :: make_tuple(5,“hello”))应返回2。 - Tomaka17
@tomaka:有没有办法在其中进行存根?返回值的意义是什么?恐怕没有例子,我至少无法很好地理解“push”。(我熟悉Lua。)你的目标是什么? (以这种形式:“调用push(...),并使___发生。” - GManNickG
我编辑了问题;你可以忽略返回值,它与我的问题无关。 - Tomaka17
显示剩余8条评论
6个回答

6

我没有编译器来测试这些代码,所以如果有问题请报告。

以下代码可以让你遍历元组并调用函数。它基于您的逻辑,进行了一些小改动。(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;
                    });

好吧。


不要误会,但我正试图避免使用模板结构。 - Tomaka17
@Tomaka:这个结构只是为了将一些 T 推入 Lua 栈中。如果你理解代码,你会发现可以用某个特定的函数替换对 pFunc 的函数调用(即删除结构)。已添加。 - GManNickG

1

不要让一个函数做两件不同的事情,而是分开处理它们:

_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);它不会试图混合这两个概念。


1

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

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

如果你发现更好的方法,请毫不犹豫地发布


0
如果你想用一个函数遍历元组,你可以使用一些样板代码来实现。其思路是构建一个可变参数整数列表,对应于元组的索引,然后使用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)... );
}

这将展开元组内容并将其提供给函数bar。

希望这可以帮到你 :)


0

这是我能想到的较为简单的解决方案之一。我已经使用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") );
}

-1

编辑通用的 for-each

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。 - KitsuneYMG

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接