working with containers in sol2
Containers are objects that are meant to be inspected and iterated and whose job is to typically provide storage to a collection of items. The standard library has several containers of varying types, and all of them have begin()
and end()
methods which return iterators. C-style arrays are also containers, and sol2 will detect all of them for use and bestow upon them special properties and functions.
- Containers from C++ are stored as
userdata
with specialusertype
metatables with :ref:`special operations<container-operations>` - In Lua 5.1, this means containers pushed without wrappers like :doc:`as_table<api/as_table>` and :doc:`nested<api/nested>` will not work with
pairs
or other built-in iteration functions from Lua - Lua 5.2+ will behave just fine (does not include LuaJIT 2.0.x)
- In Lua 5.1, this means containers pushed without wrappers like :doc:`as_table<api/as_table>` and :doc:`nested<api/nested>` will not work with
- You must push containers into C++ by returning them directly and getting/setting them directly, and they will have a type of
sol::type::userdata
and treated like a usertype
- Containers from C++ are stored as
- Containers can be manipulated from both C++ and Lua, and, like userdata, will reflect changes if you use a reference to the data.
- This means containers do not automatically serialize as Lua tables
- Either stores the container (copies/moves the container in) or hold a reference to it (when using
std::reference_wrapper<...>
/std::ref(...)
) - If you need tables, consider using
sol::as_table
andsol::nested
- See this table serialization example for more details
- Either stores the container (copies/moves the container in) or hold a reference to it (when using
- Lua 5.1 has different semantics for
pairs
andipairs
: be wary. See :ref:`examples down below<containers-pairs-example>` for more details - You can override container behavior by overriding :ref:`the detection trait<container-detection>` and :ref:`specializing the usertype_container template<usertype-container>`
- You can bind typical C-style arrays, but must follow :ref:`the rules<container-c-array>`
Note
Please note that c-style arrays must be added to Lua using lua["my_arr"] = &my_c_array;
or lua["my_arr"] = std::ref(my_c_array);
to be bestowed these properties. No, a plain T*
pointer is not considered an array. This is important because lua["my_string"] = "some string";
is also typed as an array (const char[n]
) and thusly we can only use std::reference_wrapper
s or pointers to the actual array types to work for this purpose.
containers are detected by the type trait sol::is_container<T>
. If that turns out to be true, sol2 will attempt to push a userdata into Lua for the specified type T
, and bestow it with some of the functions and properties listed below. These functions and properties are provided by a template struct sol::usertype_container<T>
, which has a number of static Lua C functions bound to a safety metatable. If you want to override the behavior for a specific container, you must first specialize sol::is_container<T>
to drive from std::true_type
, then override the functions you want to change. Any function you do not override will call the default implementation or equivalent. The default implementation for unrecognized containers is simply errors.
You can also specialize sol::is_container<T>
to turn off container detection, if you find it too eager for a type that just happens to have begin
and end
functions, like so:
struct not_container {
void begin() {
}
void end() {
}
};
namespace sol {
template <>
struct is_container<not_container> : std::false_type {};
}
This will let the type be pushed as a regular userdata.
Note
Pushing a new :doc:`usertype<api/usertype>` will prevent a qualifying C++ container type from being treated like a container. To force a type that you've registered/bound as a usertype using new_usertype
or new_simple_usertype
to be treated like a container, use :doc:`sol::as_container<api/as_container>`.
If you want it to participate as a table, use std::true_type
instead of std::false_type
from the :ref:`containter detection<container-detection>` example. and provide the appropriate iterator
and value_type
definitions on the type. Failure to do so will result in a container whose operations fail by default (or compilation will fail).
If you need a type whose declaration and definition you do not have control over to be a container, then you must override the default behavior by specializing container traits, like so:
struct not_my_type { ... };
namespace sol {
template <>
struct is_container<not_my_type> : std::true_type {};
template <>
struct usertype_container<not_my_type> {
...
// see below for implemetation details
};
}
The various operations provided by usertype_container<T>
are expected to be like so, below. Ability to override them requires familiarity with the Lua stack and how it operates, as well as knowledge of Lua's :ref:`raw C functions<raw-function-note>`. You can read up on raw C functions by looking at the "Programming in Lua" book. The online version's information about the stack and how to return information is still relevant, and you can combine that by also using sol's low-level :doc:`stack API<api/stack>` to achieve whatever behavior you need.
Warning
Exception handling WILL be provided around these particular raw C functions, so you do not need to worry about exceptions or errors bubbling through and handling that part. It is specifically handled for you in this specific instance, and ONLY in this specific instance. The raw note still applies to every other raw C function you make manually.
When you push a container into sol2, the default container handler deals with the containers by inspecting various properties, functions, and type definitions on them. Here are the broad implications of containers sol2's defaults will recognize, and which already-known containers fall into their categories:
container type | requirements | known containers | notes/caveats |
sequence | erase(iterator)
push_back /insert(value_type) |
std::vector std::deque std::list std::forward_list |
|
fixed | lacking push_back /insert
lacking erase |
std::array<T, n> T[n] (fixed arrays) |
|
ordered | key_type typedef
erase(key)
find(key)
insert(key) |
std::set std::multi_set |
|
associative, ordered | key_type , mapped_type typedefs
erase(key)
find(key)
insert({ key, value }) |
std::map std::multi_map | |
unordered | same as ordered | std::unordered_set std::unordered_multiset |
|
unordered, associative | same as ordered, associative | std::unordered_map std::unordered_multimap |
|
Below are the many container operations and their override points for usertype_container<T>
. Please use these to understand how to use any part of the implementation.
operation | lua syntax | usertype_container<T> extension point | stack argument order | notes/caveats |
set | c:set(key, value) |
static int set(lua_State*); |
1 self 2 key 3 value |
|
index_set | c[key] = value |
static int index_set(lua_State*); |
1 self 2 key 3 value |
|
at | v = c:at(key) |
static int at(lua_State*); |
1 self 2 index |
|
get | v = c:get(key) |
static int get(lua_State*); |
1 self 2 key |
|
index_get | v = c[key] |
static int index_get(lua_State*); |
1 self 2 key |
|
find | c:find(target) |
static int find(lua_State*); |
1 self 2 target |
|
erase | c:erase(target) |
static int erase(lua_State*); |
1 self 2 target |
|
insert | c:insert(target, value) |
1 self 2 target 3 key |
|
|
add | c:add(key, value) or c:add(value) |
static int add(lua_State*); |
1 self 2 key/value 3 value |
|
size | #c |
static int size(lua_State*); |
1 self |
|
clear | c:clear() |
static int clear(lua_State*); |
1 self |
|
offset | n/a | static int index_adjustment(lua_State*, T&); |
n/a |
|
begin | n/a | static iterator begin(lua_State*, T&); |
n/a |
|
end | n/a | static iterator end(lua_State*, T&); |
n/a |
|
next | static int next(lua_State*); |
1 self |
|
|
pairs | static int pairs(lua_State*); |
1 self |
|
|
ipairs | static int ipairs(lua_State*); |
1 self |
|
Note
If your type does not adequately support begin()
and end()
and you cannot override it, use the sol::is_container
trait override along with a custom implementation of pairs
on your usertype to get it to work as you want it to. Note that a type not having proper begin()
and end()
will not work if you try to forcefully serialize it as a table (this means avoid using :doc:`sol::as_table<api/as_table>` and :doc:`sol::nested<api/nested>`, otherwise you will have compiler errors). Just set it or get it directly, as shown in the examples, to work with the C++ containers.
Note
Overriding the detection traits and operation traits listed above and then trying to use sol::as_table
or similar can result in compilation failures if you do not have a proper begin()
or end()
function on the type. If you want things to behave with special usertype considerations, please do not wrap the container in one of the special table-converting/forcing abstractions.
Here's a complete working example of it working for Lua 5.3 and Lua 5.2, and how you can retrieve out the container in all versions:
.. literalinclude:: ../../examples/source/containers.cpp :name: containers-example :linenos:
Note that this will not work well in Lua 5.1, as it has explicit table checks and does not check metamethods, even when pairs
or ipairs
is passed a table. In that case, you will need to use a more manual iteration scheme or you will have to convert it to a table. In C++, you can use :doc:`sol::as_table<api/as_table>` when passing something to the library to get a table out of it: lua["arr"] = as_table( std::vector<int>{ ... });
. For manual iteration in Lua code without using as_table
for something with indices, try:
for i = 1, #vec do
print(i, vec[i])
end
There are also other ways to iterate over key/values, but they can be difficult AND cost your performance due to not having proper support in Lua 5.1. We recommend that you upgrade to Lua 5.2 or 5.3 if this is integral to your infrastructure.
If you can't upgrade, use the "member" function my_container:pairs()
in Lua to perform iteration:
.. literalinclude:: ../../examples/source/container_with_pairs.cpp :name: containers-pairs-example :linenos: