lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


Thanks for getting back to me, I hope I'm explaining this right! Here goes...

If you pop a number from something that is not a number, a few things can happen depending on what you ask the library for.
1a. "int v = binding_magic.get("not_a_number");" -- luaL_error is called when safeties are on.
1b. "sol::optional<int> maybe_v = binding_magic.get("not_a_number");" -- no error is called, simply return something that presents "this didn't work"

This can also be used with smart pointers. For #2, the "general case" because different because the binding magic library does not return a value in the case of a userdata: it returns a reference/pointer. Let's say we have "v", which is a shared pointer. The library will be okay with the following:

2a. some_type* v_ptr = binding_magic["v"]; // ok
2b. some_type& v_ref = binding_magic["v"]; // ok
2c. std::shared_ptr<some_type>& v_shared_ref = binding_magic["v"]; // ok
2d. std::shared_ptr<some_type> v_shared_value = binding_magic["v"]; // ok
2e. sol::optional<std::shared_ptr<some_type>> maybe_v_shared_value = binding_magic["v"]; // ok

Now, let's assume "v" is "nil". Of those 5 forms:

[ 2a ] We can handle the "nil" case, and return a nullptr safely
[ 2b ] We cannot convert "nil" to a reference: errors
[ 2c ] We cannot convert "nil" to a shared_ptr (lua_touserdata returns nullptr): errors
[ 2d ] Same error as 2c (even though it is a value, the conversion function returns a reference and the user makes a copy when it reaches the top-level and gets converted to a value by the user specifying a type)
[ 2e ] We can handle the "nil" case, and return a "this optional does not have stuff"

When "nil" is present, only the forms [ 2a ] and [ 2e ] are valid ways of working with it. I am a little disappointed about [ 2d ], because it's a value and we should _reasonably_ be able to create a nullptr-containing object (default-constructed, as you say), but I cannot differentiate between someone asking for a reference std::shared_ptr, versus a value std::shared_ptr (it's a C++ quirk about conversions that would probably not be useful to fully discuss on the list). That means I can't construct something on the stack and then return it, because I would ultimately be returning a dangling reference when this conversion is done.

To reiterate what I said before, 2d confused some people because when a function return a "std::shared_ptr<some_type>(nullptr)" and then they retrieved a "std::shared_ptr" as an argument or something later, they expected a shared_ptr value to be made that was a nullptr. My advice to them was not to take the shared_ptr, just take a raw pointer, but sometimes APIs are fixed and some people do not want to write wrapper functions just to communicate through my library.

Hencewhy, I had a Second Idea, where I would NOT "lua_pushnil" but instead construct a shared_ptr that was filled with nothing (nullptr) and store that as a userdata in Lua. This made all the conversions work, at the cost of "my_userdata == nil" no longer evaluating to true (hence the reason I started this thread).

Does that make sense?


On Fri, Jun 30, 2017 at 10:48 AM, Viacheslav Usov <via.usov@gmail.com> wrote:
On Fri, Jun 30, 2017 at 4:09 PM, ThePhD <jm3689@columbia.edu> wrote:

The way I handled it at first with the "binding magic" was this:
> 1. check if the pointer is null
> 2a. Yes -> push Lua's nil with `lua_pushnil(L)` (what sol::nil represents in C++ land)
> 2b. No -> push userdata

So I did misunderstood things a bit, but now I think I get the picture.

I'd say there are two questions for you to answer:

Q1. What should I do if the user wants to pop a smart pointer, but the value is incompatible? E.g., a number where a shared_ptr is expected? I am not a user of your library, so I just do not know. An exception that bubbles up as a Lua error would not be unreasonable here.

Q2. Do I need to handle the special nil case differently from the general case #1?

#2 can be considered independently of #1. Inside your library pop code, you know that it might be nil, because that is how it is pushed in case 2a. You can therefore construct an empty smart pointer (or, if your code is more generic than this, default-construct an object) and give it to the user code. Perhaps you can just construct it on stack, because, if the user code changes that object, it will probably be impossible for you to inject it into where the nil came from.

Cheers,
V.