Another policy class - converting tables to/from STL

View: New views
1 Messages — Rating Filter:   Alert me  

Another policy class - converting tables to/from STL

by Dan Posluns-2 :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

I've written another (in my opinion, very useful) policy class. This one
allows Lua tables to work interchangeably with many STL containers,
including vector, deque, list, map, multimap and hash_map. (Currently
only supports pair associative containers, and does not support simple
associative containers such as set, multiset and hash_set.) So I could
for example call the following C++ function:

void printStrings(const std::vector<std::string> &values);

... from Lua with the following command:

printStrings( { "Hello", "World" } )

Likewise I could have a C++ return a std::vector and have it
automatically converted to an array (table) in Lua.

The copy_table policy allows you to specify an input parameter or result
that you want converted to/from an STL sequence (anything that supports
push_back(), including vector, deque and list).

The copy_table_assoc policy allows you to specify an input parameter or
result that you want converted to/from an STL associative container
(anything that has a value_type of pair<key_type, value_type>(),
including map, multimap, hash_map and hash_multimap) *NOTE 1*

***THERE IS A COST ASSOCIATED WITH CONVERTING TO/FROM LUA.*** All
entries in the container must be copied over from their source to their
new destination format. This complexity is O(n * X), where X is the
insertion time for the type of container. Some calculations:
        - Lua table to vector, deque or list: O(n)
        - Lua table to map: O(n log n)
        - Any STL sequence to Lua table: O(n) *NOTE 2*
        - Any STL associative container to Lua table: O(n^2) *NOTE 2*

Obviously the sequences outperform the associative containers
considerably.

The "copy_*" in the naming of the two policies is to remind you that
there is a cost (in some cases, substantial) to convert to and from
tables.

***SO WHY USE IT THEN?***

Well, the most obvious reason is to provide your Lua coders with the
convenience and familiarity of being able to submit and receive tables
to/from functions. To my mind, this is a Big Deal... everything should
be made as simple as possible for scripters, and to that end they should
be able to use the built-in features of the language as much as
possible.

Another good reason is that you cannot expose a generic container class
in Luabind; you must repeat the definition for every instantiation of
the class, eg. vector<int>, vector<string>, vector<float>, etc. This is
undesirable and impractical in most cases.

You will want to weigh these considerations against the cost, and also
consider other options (such as using the return_stl_iterator policy, or
manually plumbing each function by creating a secondary function that
takes/returns a luabind::object, or creating some other kind of
beast-monster class for handling generic containers).

But at least now you've got the option.

*NOTE 1*: Although the copy_table_assoc policy syntactically supports
Multiple Associative Containers such as multimap and hash_multimap,
these have little functional meaning when going to/from Lua as Lua
tables are Unique Associative Containers. So it may still be useful if
you are attempting to plumb a function that takes/returns a multimap,
but keep in mind that when going from Lua to CPP the CPP function will
only ever get unique values, and (more dangerously) when going from CPP
to Lua any duplicate entries in the multimap will be discarded.

*NOTE 2*: Theoretical based on Lua 5.0 hybrid-table implementation; not
actually tested. Associative containers where the key-type is int are
assumed to have sparse values (eg. keys more likely to be 10, 500 and
10000 than 1, 2 and 3); it won't hurt but it wastes some memory if they
aren't. Although the n^2 complexity of returning a map to Lua is scary,
it's also worst-case... the average is likely much better. (Still a good
idea to keep your maps on the smaller side or favour sequences instead.)

Anyway, hopefully this will be of use to the community. Let me know if
you have any questions or comments.

Dan.




***SAMPLE CODE***

-------------------C++-------------------
static void TestIn(const vector<string> &in)
{
        for (int i = 0; i < in.size(); i++)
        {
                printf("%s\n", in[i].c_str());
        }
}

static vector<QString> TestOut()
{
        vector<QString> result;

        result.push_back("One");
        result.push_back("Two");
        result.push_back("Three");
        result.push_back("Four");

        return result;
}

static void TestAssocIn(const map<string, int> &in)
{
        for (map<string, int>::const_iterator itr = in.begin(); itr !=
in.end(); ++itr)
        {
                printf("%s -> %d\n", itr->first.c_str(), itr->second);
        }
}

static map<string, int> TestAssocOut()
{
        map<string, int> result;

        result["Hello"] = 123;
        result["World"] = 456;

        return result;
}

module(L)
[
        def("TestIn", &TestIn, copy_table(_1)),
        def("TestOut", &TestOut, copy_table(result)),
        def("TestAssocIn", &TestAssocIn, copy_table_assoc(_1)),
        def("TestAssocOut", &TestAssocOut, copy_table_assoc(result))
];

-------------------Lua-------------------
-- Test sequences
arr = { "Testing", "An", "Array" }
TestIn(arr)

print("")
result = TestOut()
for k, v in ipairs(result) do
        print(k .. ": " .. v)
end

-- Test associative arrays
print("")
arr = { Hello = 50000, World = 2 }
TestAssocIn(arr)

print("")
result = TestAssocOut()
for k, v in pairs(result) do
        print(k .. ": " .. v)
end

-------------------Output-------------------
Testing
An
Array

1: One
2: Two
3: Three
4: Four

Hello -> 50000
World -> 2

Hello: 123
World: 456




***POLICY SOURCE***
Here are the contents of table_policy.hpp:

#ifndef LUABIND_TABLE_POLICY_HPP_INCLUDED
#define LUABIND_TABLE_POLICY_HPP_INCLUDED

#include <luabind/config.hpp>
#include <luabind/detail/policy.hpp>

namespace luabind { namespace detail {

        struct table_convert
        {
                template<class ContainerT>
                ContainerT apply(lua_State *L, by_value<ContainerT>
data, int index)
                {
                        luabind::object tbl(from_stack(L,
index));
                        ContainerT result;

                        for (luabind::iterator itr(tbl), end; itr !=
end; ++itr)
                        {
                                boost::optional<ContainerT::value_type>
v = object_cast_nothrow<ContainerT::value_type>(*itr);

                                if (v)
                                {
                                        result.push_back(*v);
                                }
                        }

                        return result;
                }

                template<class ContainerT>
                ContainerT apply(lua_State *L,
by_const_reference<ContainerT> data, int index)
                {
                        return apply(L, by_value<ContainerT>(), index);
                }

                template<class ContainerT>
                ContainerT apply(lua_State *L, by_reference<ContainerT>
data, int index)
                {
                        return apply(L, by_value<ContainerT>(), index);
                }

                template<class ContainerT>
                static int match(lua_State* L, by_value<ContainerT>, int
index)
                {
                        if (lua_type(L, index) == LUA_TTABLE)
                        {
                                return 0;
                        }

                        return -1;
                }

                template<class ContainerT>
                static int match(lua_State* L,
by_const_reference<ContainerT>, int index)
                {
                        return match(L, by_value<ContainerT>(), index);
                }

                template<class ContainerT>
                static int match(lua_State* L, by_reference<ContainerT>,
int index)
                {
                        return match(L, by_value<ContainerT>(), index);
                }

                template<class ContainerT>
                void apply(lua_State *L, const ContainerT &container)
                {
                        lua_createtable(L, container.size(), 0);

                        luabind::object tbl(from_stack(L, -1));
                        int n = 0;

                        for (ContainerT::const_iterator itr =
container.begin(); itr != container.end(); ++itr)
                        {
                                tbl[++n] = *itr;
                        }
                }

                template<class T>
                void converter_postcall(lua_State*, T, int) {}
        };

        struct table_convert_assoc
        {
                template<class ContainerT>
                ContainerT apply(lua_State *L, by_value<ContainerT>
data, int index)
                {
                        luabind::object tbl(from_stack(L,
index));
                        ContainerT result;

                        for (luabind::iterator itr(tbl), end; itr !=
end; ++itr)
                        {
                                typedef ContainerT::key_type
KeyT;
                                typedef ContainerT::mapped_type
ValueT;

                                boost::optional<KeyT> k =
object_cast_nothrow<KeyT>(itr.key());

                                if (k)
                                {
                                        boost::optional<ValueT>
v = object_cast_nothrow<ValueT>(*itr);

                                        if (v)
                                        {
       
result.insert(std::make_pair(*k, *v));
                                        }
                                }
                        }

                        return result;
                }

                template<class ContainerT>
                ContainerT apply(lua_State *L,
by_const_reference<ContainerT> data, int index)
                {
                        return apply(L, by_value<ContainerT>(), index);
                }

                template<class ContainerT>
                ContainerT apply(lua_State *L, by_reference<ContainerT>
data, int index)
                {
                        return apply(L, by_value<ContainerT>(), index);
                }

                template<class ContainerT>
                static int match(lua_State* L, by_value<ContainerT>, int
index)
                {
                        if (lua_type(L, index) == LUA_TTABLE)
                        {
                                return 0;
                        }

                        return -1;
                }

                template<class ContainerT>
                static int match(lua_State* L,
by_const_reference<ContainerT>, int index)
                {
                        return match(L, by_value<ContainerT>(), index);
                }

                template<class ContainerT>
                static int match(lua_State* L, by_reference<ContainerT>,
int index)
                {
                        return match(L, by_value<ContainerT>(), index);
                }

                template<class ContainerT>
                void apply(lua_State *L, const ContainerT &container)
                {
                        lua_createtable(L, 0, container.size());

                        luabind::object tbl(from_stack(L, -1));
                        int n = 0;

                        for (ContainerT::const_iterator itr =
container.begin(); itr != container.end(); ++itr)
                        {
                                tbl[itr->first] = itr->second;
                        }
                }


                template<class T>
                void converter_postcall(lua_State*, T, int) {}
        };

        template<int N, bool Assoc>
        struct table_policy : conversion_policy<N>
        {
                struct only_accepts_values_or_references {};

                static void precall(lua_State*, const index_map&) {}
                static void postcall(lua_State*, const index_map&) {}

                template<class T, class Direction>
                struct apply
                {
                        static const bool IsPtr =
luabind::detail::is_nonconst_pointer<T>::value ||
luabind::detail::is_const_pointer<T>::value;

                        typedef typename boost::mpl::if_c<
                                        IsPtr,
       
only_accepts_values_or_references,
                                        typename boost::mpl::if_c<Assoc,
table_convert_assoc, table_convert>::type
                                >::type type;
                };
        };
}}

namespace luabind
{
        template<int N>
        detail::policy_cons<detail::table_policy<N, false>,
detail::null_type>
        copy_table(LUABIND_PLACEHOLDER_ARG(N))
        {
                return detail::policy_cons<detail::table_policy<N,
false>, detail::null_type>();
        }

        template<int N>
        detail::policy_cons<detail::table_policy<N, true>,
detail::null_type>
        copy_table_assoc(LUABIND_PLACEHOLDER_ARG(N))
        {
                return detail::policy_cons<detail::table_policy<N,
true>, detail::null_type>();
        }
}

#endif // LUABIND_TABLE_POLICY_HPP_INCLUDED


-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft
Defy all challenges. Microsoft(R) Visual Studio 2008.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/
_______________________________________________
luabind-user mailing list
luabind-user@...
https://lists.sourceforge.net/lists/listinfo/luabind-user
LightInTheBox - Buy quality products at wholesale price