Stack interaction

A fundamental aspect of this is the abstract template Value. Every type which can be pushed onto or read from the stack has a specialization of it. Useful implementations are provided out of the box:

C++ type Pushable Readable Lua type
bool yes yes boolean
signed char yes yes number (integer since 5.3)
signed short yes yes number (integer since 5.3)
signed int yes yes number (integer since 5.3)
signed long int yes yes number (integer since 5.3)
signed long long int yes yes number (integer since 5.3)
unsigned char yes yes number (integer since 5.3)
unsigned short yes yes number (integer since 5.3)
unsigned int yes yes number (integer since 5.3)
unsigned long int yes yes number (integer since 5.3)
unsigned long long int yes yes number (integer since 5.3)
float yes yes number
double yes yes number
long double yes yes number
const char* yes yes string
std::string yes yes string
std::nullptr_t yes yes nil
std::tuple<T...> yes no depends on the tuple contents
std::vector<T> yes yes table
std::list<T> yes yes table
std::map<K, V> yes yes table
lua_CFunction yes no function, table or userdata
NativeFunction yes yes function
Table yes yes table

Note: Some numeric types have a different size than their matching Lua type - they will be truncated during push or read operations.

Extending supported types

If you are missing a type that cannot be used as a user type, you can add a specialization of Value. All you need to do is modify the following snippet for your type T.

namespace luwra {
    template <>
    struct Value<T> {
        static inline
        T read(State* state, int index) {
            return /* Return the instance of T that you have read at the given index */;
        }

        static inline
        size_t push(State* state, const T& value) {
            // Push the given value on top of the stack
            return /* Return how many values you have pushed onto the stack */;
        }
    };
}

Pushing C++ values

When pushing values onto the stack you can either use Value<T>::push or the more convenient push.

// Push an integer
luwra::push(lua, 1338);

// Push a number
luwra::push(lua, 13.37);

// Push a boolean
luwra::push(lua, false);

// Push a string
luwra::push(lua, "Hello World");

// Push a table
luwra::push(lua, luwra::MemberMap {
    {"one", 1},
    {1, "one"},
    {"nested", luwra::MemberMap {
        {"more", "fields"}
    }}
});

Note: luwra::MemberMap is an alias for std:map<luwra::Pushable, luwra::Pushable>. Its keys and values are constructible using any pushable type.

This produces the following stack layout:

Absolute Position Relative Position Value
1 -5 1338
2 -4 13.37
3 -3 false
4 -2 "Hello World"
5 -1 {one = 1, [1] = "one", nested = {more = "fields"}}

It is possible to provide a template parameter to push to enforce pushing a specific type. In most cases you are probably better off by letting the compiler infer the template parameter.

Reading Lua values

Simple retrieval of Lua values is done using read<T>. Consider the stack layout from the previous example. This is how you would retrieve a value from the stack.

// Retrieve the integer at position 1
int value = luwra::read<int>(lua, 1);

// Similiar with a relative index
int value = luwra::read<int>(lua, -5);

Read and type errors

What happens when a value mismatches the expected type or cannot be converted to it? Most Value specializations use Lua's luaL_check* functions to retrieve the values from the stack. This means that no exceptions will be thrown - instead the error handling is delegated to the Lua VM. Have a look at the error handling documentation for more information.