vinniefalco/LuaBridge

How to return multiple results from C++ to Lua

m5ls7h opened this issue · 5 comments

Is it possible in LuaBridge to return multiple values from a C++ method to be used in Lua like

Multiple Results
An unconventional but quite convenient feature of Lua is that functions can return multiple results. Several
predefined functions in Lua return multiple values. We have already seen the function string.find,
which locates a pattern in a string. This function returns two indices when it finds the pattern: the index of
the character where the match starts and the one where it ends. A multiple assignment allows the program
to get both results:

  s, e = string.find("hello Lua users", "Lua")
  print(s, e)   --> 7      9

Which type must be returned by the C++ method for this?

I already succeeded returning objects by value but with the Lua multiple results syntax, I do not need to declare a new class for new return types.

There are options.

  1. You can return a table from a Lua function and get returned values as std::vector<LuaRef> in C++.
  2. The following approach: #160 (comment)

@dmitry-t Thank you for the comment. The examples call a Lua function with multiple results.
But I'm talking about the other direction: I want to write a C++ method, which can be used in a Lua script with multiple return parameters.
In cpp something like

class Foo {
    T MultipleResultsTest() {
        T.result1="hello";
        T.result2="world";
    }
}

int main(int argc, char** argv) {
 luabridge::getGlobalNamespace(L)
                .beginClass<Foo>("Foo")
                    .addConstructor<void(*) ()>()
                    .addFunction("MultipleResultsTest", &MultipleResultsTest)
                 .endClass();
}

with usage like

local a_foo = Foo()
result1, result2=a_foo:MultipleResultsTest()

But I don't know which Type T to use as return value in T MultipleResultsTest().

Sorry, I didn't quite get the question.
C++ has no multiple return values feature.

So you can:

  1. Return std::vector<LuaRef>. In Lua the result may be treated as a table and unpacked: result1, result2 = table.unpack(a_foo:ReturnVectorAsTable()).
  2. Use a C-function, that puts all return values onto the stack and returns the number of the values.
int MultipleReturnValues(lua_State* L, int nargs)
{
    auto arg1 = luabridge::Stack<Arg1>::get(L, 1);
    auto arg2 = luabridge::Stack<Arg1>::get(L, 2);
    ...
    luabridge::Stack<Ret1>::push(L, ret1);
    luabridge::Stack<Ret2>::push(L, ret2);
    ...
    return nresults;
}

As you can see, in both cases you have to push return values one by one (into a vector on onto the stack).

I guess it's also possible to implement more explicit approach with the std::tuple. I'll think about it.

@dmitry-t Thank you for the detailed examples. I now understand that it is difficult to provide a generic solution. Both examples require, that the C++ classes implement Lua specific parameter handling. I use the C++ classes in the C++ context as well as in the Lua context. Therefore, I don't want to have Lua specifics in the class interfaces.
LuaBridge does a great job in exposing C++ classes without Lua specifics to Lua scripts.
So I will stick to my current approach in defining utility classes for return parameters like

struct MultipleResults
{
    std::string result1;
    std::string result2;
};

class Foo {
    MultipleResults MultipleResultsTest() {
        multipleResults.result1="hello";
        multipleResults.result2="world";
        return multipleResults;
    }
}

int main(int argc, char** argv) {
 luabridge::getGlobalNamespace(L)
                .beginClass<MultipleResults>("MultipleResults")
                    .addData("result1", &MultipleResults::result1)
                    .addData("result2", &MultipleResults::result1)
                .endClass() 
                .beginClass<Foo>("Foo")
                    .addConstructor<void(*) ()>()
                    .addFunction("MultipleResultsTest", &Foo::MultipleResultsTest)
                 .endClass();
}

and usage like

local a_foo = Foo()
local multipleresults=a_foo:MultipleResultsTest()
print(multipleresults.result1)
print(multipleresults.result2)

Maybe a generic approach like std::vector<std::tuple> or std::map<std::string,std::string> is useful in some cases. But this still would not solve the problem of mixed types.

@m5ls7h You are welcome.
Indeed, returning an object is the most natural solution for C++.

P.S. std::tuple<> is a template with variadic parameters so its instance may look like std::tuple<std::string, int, float> which means we can have full type checking in the same way as for structures. Basically the tuple is a replacement for structures with anonymous fields. I guess it's worth implementing.