C++0x: iterating through a tuple with a function

a 夏天 提交于 2019-12-10 09:43:13

问题


I have a function named _push which can handle different parameters, including tuples, and is supposed to return the number of pushed elements.

For example, _push(5) should push '5' on the stack (the stack of lua) and return 1 (because one value was pushed), while _push(std::make_tuple(5, "hello")) should push '5' and 'hello' and return 2.

I can't simply replace it by _push(5, "hello") because I sometimes use _push(foo()) and I want to allow foo() to return a tuple.

Anyway I can't manage to make it work with tuples:

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

Let's say you want to push a tuple<int,bool>. This is how I expect it to work:

  • _push<{int,bool}, 2> is called (first definition)
  • _push<{int,bool}, 1> is called (first definition)
  • _push<{int,bool}, 0> is called (second definition)

However with g++ 4.5 (the only compiler I have which supports variadic templates), I get an error concerning _push<Args...,N-1>(t) (line 3) saying that it couldn't find a matching function to call (without any further detail). I tried without the "..." but I get another error saying that the parameters pack is not expanded.

How can I fix this?

PS: I know that you can do this using a template struct (this is in fact what I was doing before), but I'd like to know how to do it with a function

PS 2: PS2 is solved, thanks GMan


回答1:


I don't have a compiler to test any of this, so you'll have to report any issues.

The following should allow you to iterate across a tuple calling a function. It's based off your logic, with a few minor changes. (N is a std::size_t, it's the first parameter to allow Args (and Func) to be deduced on further calls, it just calls some function instead of performing a specific task). Nothing too drastic:

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

Assuming that all works, you then just have:

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

And lastly:

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

Let me know if that all makes sense.


Since you seem to really be seriously against structs for some reason, just make a function like this:

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

And change everything to specifically call that function:

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

No idea why you'd avoid generic functionality though, or be so against structs.


Oh how nice polymorphic lambda's would be. Ditch the utility push_lua_stack class and just write:

std::size_t count = 0;

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

Oh well.




回答2:


I solved the problem with some hacks. Here is the code:

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

Don't hesitate to post if you find a better way




回答3:


Instead of making one function do two different things, separate the concerns:

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

Then the problem simply no longer exists for _push! You have no ambiguity about whether to push one item containing multiple values (I'm unfamiliar with Lua, but I know it has a primary container class) or push multiple items from one sequence.

Renaming the functions may be helpful:

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

For comparison, consider how std::vector always uses push_back for one item (_append) and insert for multiple items (_extend); it doesn't try to mix the two concepts.




回答4:


If you want to iterate through a tuple with a function, you can do so with a (litte) bit of boilerplate. The idea is to build a variadic integer list corresponding to tuple indices, then use std::get to access values. Quickly:

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

Then you can expand a tuple like this:

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

This will expand tuple content and feed it to function bar.

Hope this helps :)




回答5:


This is one of the simpler solutions I can think of. I tested it successfully with 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:


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

Example Usage

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 << ']';


来源:https://stackoverflow.com/questions/3316573/c0x-iterating-through-a-tuple-with-a-function

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!