containers¶
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
userdatawith specialusertypemetatables with special operations - In Lua 5.1, this means containers pushed without wrappers like as_table and nested will not work with
pairsor other built-in iteration functions from Lua Lua 5.2+ will behave just fine (does not include LuaJIT 2.0.x)
If this behaviour is needed using LuaJIT, the compilation flag LUAJIT_ENABLE_LUA52COMPAT can be used.
- In Lua 5.1, this means containers pushed without wrappers like as_table and 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::userdataand 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_tableandsol::nestedSee this table serialization example for more details
Lua 5.1 has different semantics for
pairsandipairs: be wary. See examples down below for more detailsYou can override container behavior by overriding the detection trait and specializing the usertype_container template
You can bind typical C-style arrays, but must follow the rules
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_wrappers or pointers to the actual array types to work for this purpose.
container detection¶
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 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 sol::as_container.
container overriding¶
If you want it to participate as a table, use std::true_type instead of std::false_type from the containter 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 raw C functions. 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 stack API 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.
container classifications¶
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 |
|
std::vector std::deque std::list std::forward_list |
|
fixed |
lacking |
std::array<T, n> T[n] (fixed arrays) |
|
ordered |
|
std::set std::multi_set |
|
associative, ordered |
|
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 |
|
container operations¶
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 |
|
|
1 self 2 key 3 value |
|
index_set |
|
|
1 self 2 key 3 value |
|
at |
|
|
1 self 2 index |
|
get |
|
|
1 self 2 key |
|
index_get |
|
|
1 self 2 key |
|
find |
|
|
1 self 2 target |
|
erase |
|
|
1 self 2 target |
|
insert |
|
1 self 2 target 3 key |
|
|
add |
|
|
1 self 2 key/value 3 value |
|
size |
|
|
1 self |
|
clear |
|
|
1 self |
|
offset |
n/a |
|
n/a |
|
begin |
n/a |
|
n/a |
|
end |
n/a |
|
n/a |
|
next |
|
1 self |
|
|
pairs |
|
1 self |
|
|
ipairs |
|
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 sol::as_table and sol::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.
a complete example¶
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:
1#define SOL_ALL_SAFETIES_ON 1
2#include <sol/sol.hpp>
3
4#include <vector>
5#include <iostream>
6
7int main(int, char**) {
8 std::cout << "=== containers ===" << std::endl;
9
10 sol::state lua;
11 lua.open_libraries();
12
13 lua.script(R"(
14function f (x)
15 print("container has:")
16 for k=1,#x do
17 v = x[k]
18 print("\t", k, v)
19 end
20 print()
21end
22 )");
23
24 // Have the function we
25 // just defined in Lua
26 sol::function f = lua["f"];
27
28 // Set a global variable called
29 // "arr" to be a vector of 5 lements
30 lua["arr"] = std::vector<int> { 2, 4, 6, 8, 10 };
31
32 // Call it, see 5 elements
33 // printed out
34 f(lua["arr"]);
35
36 // Mess with it in C++
37 // Containers are stored as userdata, unless you
38 // use `sol::as_table()` and `sol::as_table_t`.
39 std::vector<int>& reference_to_arr = lua["arr"];
40 reference_to_arr.push_back(12);
41
42 // Call it, see *6* elements
43 // printed out
44 f(lua["arr"]);
45
46 lua.script(R"(
47arr:add(28)
48 )");
49
50 // Call it, see *7* elements
51 // printed out
52 f(lua["arr"]);
53
54 lua.script(R"(
55arr:clear()
56 )");
57
58 // Now it's empty
59 f(lua["arr"]);
60
61 std::cout << std::endl;
62
63 return 0;
64}
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 sol::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:
1for i = 1, #vec do
2 print(i, vec[i])
3end
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:
1#define SOL_ALL_SAFETIES_ON 1
2#include <sol/sol.hpp>
3
4
5#include <unordered_set>
6#include <iostream>
7
8int main() {
9 struct hasher {
10 typedef std::pair<std::string, std::string>
11 argument_type;
12 typedef std::size_t result_type;
13
14 result_type operator()(const argument_type& p) const {
15 return std::hash<std::string>()(p.first);
16 }
17 };
18
19 using my_set = std::unordered_set<
20 std::pair<std::string, std::string>,
21 hasher>;
22
23 std::cout << "=== containers with std::pair<> ==="
24 << std::endl;
25
26 sol::state lua;
27 lua.open_libraries(sol::lib::base);
28
29 lua.set_function("f", []() {
30 return my_set { { "key1", "value1" },
31 { "key2", "value2" },
32 { "key3", "value3" } };
33 });
34
35 lua.safe_script("v = f()");
36 lua.safe_script("print('v:', v)");
37 lua.safe_script("print('#v:', #v)");
38 // note that using my_obj:pairs() is a
39 // way around pairs(my_obj) not working in Lua 5.1/LuaJIT:
40 // try it!
41 lua.safe_script(
42 "for k,v1,v2 in v:pairs() do print(k, v1, v2) end");
43
44 std::cout << std::endl;
45
46 return 0;
47}