Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

embind: Add helper for registering a std::optional type. #21076

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
21 changes: 18 additions & 3 deletions site/source/docs/porting/connecting_cpp_and_javascript/embind.rst
Expand Up @@ -933,14 +933,15 @@ Out of the box, *embind* provides converters for many standard C++ types:
\*\*Requires BigInt support to be enabled with the `-sWASM_BIGINT` flag.

For convenience, *embind* provides factory functions to register
``std::vector<T>`` (:cpp:func:`register_vector`) and ``std::map<K, V>``
(:cpp:func:`register_map`) types:
``std::vector<T>`` (:cpp:func:`register_vector`), ``std::map<K, V>``
(:cpp:func:`register_map`), and ``std::optional<T>`` (:cpp:func:`register_optional`) types:

.. code:: cpp

EMSCRIPTEN_BINDINGS(stl_wrappers) {
register_vector<int>("VectorInt");
register_map<int,int>("MapIntInt");
register_optional<std::string>("Optional);
}

A full example is shown below:
Expand All @@ -950,6 +951,7 @@ A full example is shown below:
#include <emscripten/bind.h>
#include <string>
#include <vector>
#include <optional>

using namespace emscripten;

Expand All @@ -964,13 +966,20 @@ A full example is shown below:
return m;
}

std::optional<std::string> returnOptionalData() {
return "hello";
}

EMSCRIPTEN_BINDINGS(module) {
function("returnVectorData", &returnVectorData);
function("returnMapData", &returnMapData);
function("returnOptionalData", &returnOptionalData);

// register bindings for std::vector<int> and std::map<int, std::string>.
// register bindings for std::vector<int>, std::map<int, std::string>, and
// std::optional<std::string>.
register_vector<int>("vector<int>");
register_map<int, std::string>("map<int, string>");
register_optional<std::string>();
}


Expand Down Expand Up @@ -1017,6 +1026,12 @@ The following JavaScript can be used to interact with the above C++.
// reset the value at the given index position
retMap.set(10, "OtherValue");

// Optional values will return undefined if there is no value.
var optional = Module['returnOptionalData']();
if (optional !== undefined) {
console.log(optional);
}


TypeScript Definitions
======================
Expand Down
5 changes: 5 additions & 0 deletions src/embind/embind.js
Expand Up @@ -691,6 +691,11 @@ var LibraryEmbind = {
__embind_register_emval(rawType, name);
},

_embind_register_optional__deps: ['_embind_register_emval'],
_embind_register_optional: (rawOptionalType, rawType) => {
__embind_register_emval(rawOptionalType, "");
},

_embind_register_memory_view__deps: ['$readLatin1String', '$registerType'],
_embind_register_memory_view: (rawType, dataTypeIndex, name) => {
var typeMapping = [
Expand Down
17 changes: 17 additions & 0 deletions src/embind/embind_gen.js
Expand Up @@ -34,6 +34,12 @@ var LibraryEmbind = {
this.destructorType = 'none'; // Same as emval.
}
},
$OptionalType: class {
constructor(type) {
this.type = type;
this.destructorType = 'none'; // Same as emval.
}
},
$FunctionDefinition__deps: ['$createJsInvoker'],
$FunctionDefinition: class {
constructor(name, returnType, argumentTypes, functionIndex, thisType = null, isAsync = false) {
Expand Down Expand Up @@ -294,6 +300,7 @@ var LibraryEmbind = {
out.push('\n};\n\n');
}
},
$TsPrinter__deps: ['$OptionalType'],
$TsPrinter: class {
constructor(definitions) {
this.definitions = definitions;
Expand Down Expand Up @@ -336,6 +343,9 @@ var LibraryEmbind = {
if (type instanceof PointerDefinition) {
return this.typeToJsName(type.classType);
}
if (type instanceof OptionalType) {
return `${this.typeToJsName(type.type)} | undefined`;
}
return type.name;
}

Expand Down Expand Up @@ -461,6 +471,13 @@ var LibraryEmbind = {
name = readLatin1String(name);
registerType(rawType, new UserType(rawType, name));
},
_embind_register_optional__deps: ['_embind_register_emval', '$OptionalType'],
_embind_register_optional: (rawOptionalType, rawType) => {
whenDependentTypesAreResolved([rawOptionalType], [rawType], function(type) {
type = type[0];
return [new OptionalType(type)];
});
},
_embind_register_memory_view: (rawType, dataTypeIndex, name) => {
// TODO
},
Expand Down
1 change: 1 addition & 0 deletions src/library_sigs.js
Expand Up @@ -304,6 +304,7 @@ sigs = {
_embind_register_function__sig: 'vpippppi',
_embind_register_integer__sig: 'vpppii',
_embind_register_memory_view__sig: 'vpip',
_embind_register_optional__sig: 'vpp',
_embind_register_smart_ptr__sig: 'vpppipppppppp',
_embind_register_std_string__sig: 'vpp',
_embind_register_std_wstring__sig: 'vppp',
Expand Down
44 changes: 44 additions & 0 deletions system/include/emscripten/bind.h
Expand Up @@ -18,6 +18,9 @@
#include <string>
#include <type_traits>
#include <vector>
#if __cplusplus >= 201703L
#include <optional>
#endif

#include <emscripten/em_macros.h>
#include <emscripten/val.h>
Expand Down Expand Up @@ -249,6 +252,10 @@ void _embind_register_constant(
TYPEID constantType,
double value);

void _embind_register_optional(
TYPEID optionalType,
TYPEID type);

void _embind_register_user_type(
TYPEID type,
const char* typeName);
Expand Down Expand Up @@ -1917,6 +1924,13 @@ class_<std::vector<T>> register_vector(const char* name) {
;
}

#if __cplusplus >= 201703L
template<typename T>
void register_optional() {
internal::_embind_register_optional(internal::TypeID<std::optional<T>>::get(), internal::TypeID<T>::get());
}
#endif

////////////////////////////////////////////////////////////////////////////////
// MAPS
////////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -1973,6 +1987,36 @@ class_<std::map<K, V>> register_map(const char* name) {
;
}

////////////////////////////////////////////////////////////////////////////////
// std::optional
////////////////////////////////////////////////////////////////////////////////

#if __cplusplus >= 201703L
namespace internal {
template <typename T>
struct BindingType<std::optional<T>> {
using ValBinding = BindingType<val>;
using WireType = ValBinding::WireType;

static WireType toWireType(std::optional<T> value) {
if (value) {
return ValBinding::toWireType(val(*value));
}
return ValBinding::toWireType(val::undefined());
}


static std::optional<T> fromWireType(WireType value) {
val optional = val::take_ownership(value);
if (optional.isUndefined()) {
return {};
}
return optional.as<T>();
}
};
} // end namespace internal
#endif


////////////////////////////////////////////////////////////////////////////////
// ENUMS
Expand Down
75 changes: 75 additions & 0 deletions test/embind/embind.test.js
Expand Up @@ -1204,6 +1204,81 @@ module({
});
});

BaseFixture.extend("optional", function() {
if (!("embind_test_return_optional_int" in cm)) {
return;
}
test("std::optional works with returning int", function() {
var optional = cm.embind_test_return_optional_int(true);
assert.equal(42, optional);

optional = cm.embind_test_return_optional_int(false);
assert.equal(undefined, optional);
});

test("std::optional works with returning float", function() {
var optional = cm.embind_test_return_optional_float(true);
assert.equal(Math.fround(4.2), optional);

optional = cm.embind_test_return_optional_float(false);
assert.equal(undefined, optional);
});

test("std::optional works with returning SmallClass", function() {
var optional = cm.embind_test_return_optional_small_class(true);
assert.equal(7, optional.member);
optional.delete();

optional = cm.embind_test_return_optional_small_class(false);
assert.equal(undefined, optional);
});

test("std::optional works with returning string", function() {
var optional = cm.embind_test_return_optional_string(true);
assert.equal("hello", optional);

optional = cm.embind_test_return_optional_string(false);
assert.equal(undefined, optional);
});

test("std::optional works int arg", function() {
var value = cm.embind_test_optional_int_arg(42);
assert.equal(42, value);

value = cm.embind_test_optional_int_arg(undefined);
assert.equal(-1, value);
});

test("std::optional works float arg", function() {
var value = cm.embind_test_optional_float_arg(4.2);
assert.equal(Math.fround(4.2), value);

value = cm.embind_test_optional_float_arg(undefined);
assert.equal(Math.fround(-1.1), value);
});

test("std::optional works string arg", function() {
var value = cm.embind_test_optional_string_arg("hello");
assert.equal("hello", value);

value = cm.embind_test_optional_string_arg("");
assert.equal("", value);

value = cm.embind_test_optional_string_arg(undefined);
assert.equal("no value", value);
});

test("std::optional works SmallClass arg", function() {
var small = new cm.SmallClass();
var value = cm.embind_test_optional_small_class_arg(small);
assert.equal(7, value);
small.delete();

value = cm.embind_test_optional_small_class_arg(undefined);
assert.equal(-1, value);
});
});

BaseFixture.extend("functors", function() {
test("can get and call function ptrs", function() {
var ptr = cm.emval_test_get_function_ptr();
Expand Down
72 changes: 72 additions & 0 deletions test/embind/embind_test.cpp
Expand Up @@ -10,6 +10,10 @@
#include <emscripten/heap.h>
#include <emscripten/em_asm.h>

#if __cplusplus >= 201703L
#include <optional>
#endif

using namespace emscripten;

val emval_test_mallinfo() {
Expand Down Expand Up @@ -1298,6 +1302,59 @@ void test_string_with_vec(const std::string& p1, std::vector<std::string>& v1) {
printf("%s\n", p1.c_str());
}

#if __cplusplus >= 201703L
std::optional<int> embind_test_return_optional_int(bool create) {
if (create) {
return 42;
}
return {};
}
std::optional<float> embind_test_return_optional_float(bool create) {
if (create) {
return 4.2;
}
return {};
}
std::optional<std::string> embind_test_return_optional_string(bool create) {
if (create) {
return "hello";
}
return {};
}
std::optional<SmallClass> embind_test_return_optional_small_class(bool create) {
if (create) {
return SmallClass();
}
return {};
}

int embind_test_optional_int_arg(std::optional<int> arg) {
if (arg) {
return *arg;
}
return -1;
}
float embind_test_optional_float_arg(std::optional<float> arg) {
if (arg) {
return *arg;
}
return -1.1;
}
std::string embind_test_optional_string_arg(std::optional<std::string> arg) {
if (arg) {
return *arg;
}
return "no value";
}

int embind_test_optional_small_class_arg(std::optional<SmallClass> arg) {
if (arg) {
return arg->member;
}
return -1;
}
#endif

val embind_test_getglobal() {
return val::global();
}
Expand Down Expand Up @@ -2297,6 +2354,21 @@ EMSCRIPTEN_BINDINGS(tests) {

function("test_string_with_vec", &test_string_with_vec);

#if __cplusplus >= 201703L
register_optional<int>();
register_optional<float>();
register_optional<SmallClass>();
register_optional<std::string>();
function("embind_test_return_optional_int", &embind_test_return_optional_int);
function("embind_test_return_optional_float", &embind_test_return_optional_float);
function("embind_test_return_optional_small_class", &embind_test_return_optional_small_class);
function("embind_test_return_optional_string", &embind_test_return_optional_string);
function("embind_test_optional_int_arg", &embind_test_optional_int_arg);
function("embind_test_optional_float_arg", &embind_test_optional_float_arg);
function("embind_test_optional_string_arg", &embind_test_optional_string_arg);
function("embind_test_optional_small_class_arg", &embind_test_optional_small_class_arg);
#endif

register_map<std::string, int>("StringIntMap");
function("embind_test_get_string_int_map", embind_test_get_string_int_map);

Expand Down