jeudi 23 juin 2016

Attaching a "policy" to a function parameter

In some code that I am writing, I have a bunch of C++ functions that I am trying to bind to lua in a generic way. (However, this question really has nothing to do with lua, it's really a C++ design question.)

The idea is I might have a C++ function with signature

int my_function(lua_State * L, std::string server, std::string message);

for instance, and I want to be able to push it to lua and expose it to user scripts.

However, lua can only directly receive functions of signature int (lua_State *). So, I have some templates which take a function pointer with signature like the above, and produce a function of signature int(lua_State *), which tries to read corresponding arguments off of the lua stack, and calls the target function with the parameters if it succeeds, and signals a lua error for the user if not.

That part is working, with some work, and this question is not about how to do that. (Please don't tell me about luabind, luabridge or other existing libs, for reasons I can't go into those are not appropriate for my project.)

Instead the issue I'm having now is that sometimes I want the input parameters to have slightly different semantics.

For instance, sometimes a parameter should be optional. I specialized my template for boost::optional in order to handle that case. So I can tag optional parameters with boost::optional in the function signature and the wrapper will know that if that parameter is missing, it's not an error, and it should just pass boost::none. Example:

int my_function(lua_State * L, boost::optional<std::string>, std::string message);

So the boost::optional template is being used kind of like a "policy" for the input here, and I basically like how that is working.

Here's an issue I'm less sure about though: handling of bool. In lua, there is a proper boolean type, however, lua also has a notion of contextually boolean, similar to C++'s notion of contextually convertible to bool. In lua, the values false and nil are falsy, and all other values are truthy.

Typically, when you have a c++ function that takes a bool, the user will expect that they can pass it any value and that your interface will respect the truthiness even if it's not strictly speaking a boolean value. However, in other cases, you might really want it to be interpretted strictly as a bool, and for it to be a user error if they don't pass true or false.

What I would like to be able to do is tag the "strictness" policy within the function declaration, so it would look like

int my_function(lua_State * L, strict<bool> b, std::string message);

Where, strict is some template like

template <typename T>
struct strict {
  T value;
};

and this template really has meaning only in my wrapper machinery.

The thing that is annoying about this is that then you have to type b.value everywhere.

I thought about doing it like this:

template <typename T>
struct strict {
  T value;

  operator T & () & { return this->value; }
  operator const T & () const & { return this->value; }
  operator T && () && { return std::move(this->value); }
};

to allow a bunch of ref-qualified implicit conversions from strict<T> to references to the value.

How unsafe is this? I don't see major safety holes in this, although I've always adhered to the "implicit conversions are evil" mantra. I played around with it a little in test code and it doesn't seem to create ambiguities or problems, but there might be a clever way to make this do something very bad that I didn't think of.

If it's not a good idea, is there a better strategy than typing b.value everywhere, or some different way of rigging up the parameter policies that won't intrude upon the types?

Aucun commentaire:

Enregistrer un commentaire