Primer includes some support for optional containers.
An optional is a container which contains either one or zero of a given
type. A popular example is boost::optional
-- a std::optional type was recently added to C++17.
Optionals are extremely useful for representing optional parameters to a function. In primer, this gives you a flexible way to allow users of your callbacks to omit certain arguments.
Pushing optionals is straightforward in primer. If the optional is empty,
then primer will push nil.
If it is occupied, then primer pushes the value.
boost::optional<int> x; primer::push(L, x); assert(lua_gettop(L) == 1); assert(lua_isnil(L, 1)); x = 7; primer::push(L, x); assert(lua_gettop(L) == 2); assert(lua_isinteger(L, 2)); assert(lua_tointeger(L, 2) == 7);
Optionals can usefully have at least two different kinds of semantics when reading: strict and relaxed.
When strictly reading optionals, only values that would round-trip with "push" are accepted, and anything that isn't nil or acceptable as an instance of the value type, is an error.
lua_pushnumber(L, 5.5f); assert(lua_gettop(L) == 1); auto result1 = primer::read<boost::optional<LUA_NUMBER>>(L, 1); assert(result1); boost::optional<LUA_NUMBER> my_number = *result1; assert(my_number); assert(*my_number == 5.5f); lua_pushnil(L); assert(lua_gettop(L) == 2); auto result2 = primer::read<boost::optional<LUA_NUMBER>>(L, 2); assert(result2); boost::optional<LUA_NUMBER> my_number2 = *result2; assert(!my_number2); // got an empty state (nil) lua_pushstring(L, "foo"); assert(lua_gettop(L) == 3); auto result3 = primer::read<boost::optional<LUA_NUMBER>>(L, 3); assert(!result3); // string is not nil or a number
The main use of course is as function parameters. Given a function taking optional parameters like so,
primer::result maximum(lua_State * L, boost::optional<int> x, boost::optional<int> y) { if (x) { if (y) { lua_pushinteger(L, std::max(*x, *y)); } else { lua_pushinteger(L, *x); } } else { if (y) { lua_pushinteger(L, *y); } else { lua_pushnil(L); } } return 1; }
these are the kinds of semantics that primer would give when it is pushed to lua:
lua_pushcfunction(L, PRIMER_ADAPT(&maximum)); lua_setglobal(L, "maximum"); luaL_loadstring(L, "local x = 5 \n" "local y = 7 \n" "assert(maximum(x, y) == 7) \n" "assert(maximum(x, nil) == 5) \n" "assert(maximum(nil, y) == 7) \n" "assert(not maximum(nil, nil)) \n" "assert(not pcall(maximum, 'foo', 'bar')) \n" "assert(not pcall(maximum, 5, 'bar')) \n" "assert(not pcall(maximum, 'foo', nil)) \n" "assert(pcall(maximum, 6, -14)) \n" "assert(pcall(maximum, 6)) \n" "assert(maximum(6) == 6) \n"); assert(LUA_OK == lua_pcall(L, 0, 0, 0));
In the relaxed semantics, we simply try to read an instance of the value type. If it succeeds, then that becomes the value of the optional. If an error results, then the optional is returned in the empty state, and the error is discarded.
This can be useful, for instance, if you have a complex function which takes inputs using a named parameter idiom, and you want it to flexibly ignore values that don't make sense. Possibly, the intention is that the user will pass some table that also has other uses within your system, and if every possible field must match exactly then the interface would be too brittle to be used comfortably.
To give a type optional semantics, we simply specialize push and read for it. The built-in semantics are implemented in a header
#include <primer/container/optional_base.hpp>
They can be enabled for an optional type, for example, as follows:
#include <boost/optional/optional.hpp> #include <primer/container/optional_base.hpp> namespace primer { namespace traits { template <typename T> struct push<boost::optional<T>> // : container::optional_push<boost::optional<T>> {}; template <typename T> struct read<boost::optional<T>> : container::optional_strict_read<boost::optional<T>> {}; } // end namespace traits } // end namespace primer
If we had selected detail::optional_relaxed_read
instead of detail::optional_strict_read, then we would get
relaxed semantics.
If you have an optional type which differs significantly from boost::optional and std::optional
in its interface, then in order to use the above traits implementing the
strict and relaxed read, you must specialize an additional trait primer::traits::optional_access for your type, in order
to tell primer how to talk to it.
namespace traits { // This trait is used to tell primer in a basic way how to interact with an // optional template type. // // optional_access<T> should provide // // - typedef value_type // - static const value_type * as_ptr(const T &) noexcept; // - static T make_empty() noexcept; // - static T from_value_type(value_type &&) noexcept; // // as_ptr should return nullptr if the optional is empty, or a pointer to the // contained value. // // make empty should create an empty optional. // from_value_type should move-construct an occupied optional. // // It must be specialized if your optional does not look like `boost::optional` // or `std::optional`. template <typename T, typename ENABLE = void> struct optional_access { using value_type = typename T::value_type; PRIMER_STATIC_ASSERT(std::is_nothrow_constructible<T>::value, "optional must be nothrow constructible"); // TODO: boost::optional doesn't do this... // PRIMER_STATIC_ASSERT(noexcept(T{std::declval<value_type>()}), // "optional must be nothrow constructible from r-value // reference to value type"); static const value_type * as_ptr(const T & t) noexcept { return t ? &*t : nullptr; } static T make_empty() noexcept { return {}; } static T from_value_type(value_type && v) noexcept { return T{std::move(v)}; } }; } // end namespace traits