Skip to content

Commit b1401d9

Browse files
authored
embind: Add helper for registering a std::optional type. (#21076)
1 parent 12e5069 commit b1401d9

File tree

9 files changed

+242
-3
lines changed

9 files changed

+242
-3
lines changed

site/source/docs/porting/connecting_cpp_and_javascript/embind.rst

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -933,14 +933,15 @@ Out of the box, *embind* provides converters for many standard C++ types:
933933
\*\*Requires BigInt support to be enabled with the `-sWASM_BIGINT` flag.
934934

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

939939
.. code:: cpp
940940
941941
EMSCRIPTEN_BINDINGS(stl_wrappers) {
942942
register_vector<int>("VectorInt");
943943
register_map<int,int>("MapIntInt");
944+
register_optional<std::string>("Optional);
944945
}
945946
946947
A full example is shown below:
@@ -950,6 +951,7 @@ A full example is shown below:
950951
#include <emscripten/bind.h>
951952
#include <string>
952953
#include <vector>
954+
#include <optional>
953955
954956
using namespace emscripten;
955957
@@ -964,13 +966,20 @@ A full example is shown below:
964966
return m;
965967
}
966968
969+
std::optional<std::string> returnOptionalData() {
970+
return "hello";
971+
}
972+
967973
EMSCRIPTEN_BINDINGS(module) {
968974
function("returnVectorData", &returnVectorData);
969975
function("returnMapData", &returnMapData);
976+
function("returnOptionalData", &returnOptionalData);
970977
971-
// register bindings for std::vector<int> and std::map<int, std::string>.
978+
// register bindings for std::vector<int>, std::map<int, std::string>, and
979+
// std::optional<std::string>.
972980
register_vector<int>("vector<int>");
973981
register_map<int, std::string>("map<int, string>");
982+
register_optional<std::string>();
974983
}
975984
976985
@@ -1017,6 +1026,12 @@ The following JavaScript can be used to interact with the above C++.
10171026
// reset the value at the given index position
10181027
retMap.set(10, "OtherValue");
10191028
1029+
// Optional values will return undefined if there is no value.
1030+
var optional = Module['returnOptionalData']();
1031+
if (optional !== undefined) {
1032+
console.log(optional);
1033+
}
1034+
10201035
10211036
TypeScript Definitions
10221037
======================

src/embind/embind.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -691,6 +691,11 @@ var LibraryEmbind = {
691691
__embind_register_emval(rawType, name);
692692
},
693693

694+
_embind_register_optional__deps: ['_embind_register_emval'],
695+
_embind_register_optional: (rawOptionalType, rawType) => {
696+
__embind_register_emval(rawOptionalType, "");
697+
},
698+
694699
_embind_register_memory_view__deps: ['$readLatin1String', '$registerType'],
695700
_embind_register_memory_view: (rawType, dataTypeIndex, name) => {
696701
var typeMapping = [

src/embind/embind_gen.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,12 @@ var LibraryEmbind = {
3434
this.destructorType = 'none'; // Same as emval.
3535
}
3636
},
37+
$OptionalType: class {
38+
constructor(type) {
39+
this.type = type;
40+
this.destructorType = 'none'; // Same as emval.
41+
}
42+
},
3743
$FunctionDefinition__deps: ['$createJsInvoker'],
3844
$FunctionDefinition: class {
3945
constructor(name, returnType, argumentTypes, functionIndex, thisType = null, isAsync = false) {
@@ -294,6 +300,7 @@ var LibraryEmbind = {
294300
out.push('\n};\n\n');
295301
}
296302
},
303+
$TsPrinter__deps: ['$OptionalType'],
297304
$TsPrinter: class {
298305
constructor(definitions) {
299306
this.definitions = definitions;
@@ -336,6 +343,9 @@ var LibraryEmbind = {
336343
if (type instanceof PointerDefinition) {
337344
return this.typeToJsName(type.classType);
338345
}
346+
if (type instanceof OptionalType) {
347+
return `${this.typeToJsName(type.type)} | undefined`;
348+
}
339349
return type.name;
340350
}
341351

@@ -461,6 +471,13 @@ var LibraryEmbind = {
461471
name = readLatin1String(name);
462472
registerType(rawType, new UserType(rawType, name));
463473
},
474+
_embind_register_optional__deps: ['_embind_register_emval', '$OptionalType'],
475+
_embind_register_optional: (rawOptionalType, rawType) => {
476+
whenDependentTypesAreResolved([rawOptionalType], [rawType], function(type) {
477+
type = type[0];
478+
return [new OptionalType(type)];
479+
});
480+
},
464481
_embind_register_memory_view: (rawType, dataTypeIndex, name) => {
465482
// TODO
466483
},

src/library_sigs.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,7 @@ sigs = {
304304
_embind_register_function__sig: 'vpippppi',
305305
_embind_register_integer__sig: 'vpppii',
306306
_embind_register_memory_view__sig: 'vpip',
307+
_embind_register_optional__sig: 'vpp',
307308
_embind_register_smart_ptr__sig: 'vpppipppppppp',
308309
_embind_register_std_string__sig: 'vpp',
309310
_embind_register_std_wstring__sig: 'vppp',

system/include/emscripten/bind.h

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818
#include <string>
1919
#include <type_traits>
2020
#include <vector>
21+
#if __cplusplus >= 201703L
22+
#include <optional>
23+
#endif
2124

2225
#include <emscripten/em_macros.h>
2326
#include <emscripten/val.h>
@@ -249,6 +252,10 @@ void _embind_register_constant(
249252
TYPEID constantType,
250253
double value);
251254

255+
void _embind_register_optional(
256+
TYPEID optionalType,
257+
TYPEID type);
258+
252259
void _embind_register_user_type(
253260
TYPEID type,
254261
const char* typeName);
@@ -1917,6 +1924,13 @@ class_<std::vector<T>> register_vector(const char* name) {
19171924
;
19181925
}
19191926

1927+
#if __cplusplus >= 201703L
1928+
template<typename T>
1929+
void register_optional() {
1930+
internal::_embind_register_optional(internal::TypeID<std::optional<T>>::get(), internal::TypeID<T>::get());
1931+
}
1932+
#endif
1933+
19201934
////////////////////////////////////////////////////////////////////////////////
19211935
// MAPS
19221936
////////////////////////////////////////////////////////////////////////////////
@@ -1973,6 +1987,36 @@ class_<std::map<K, V>> register_map(const char* name) {
19731987
;
19741988
}
19751989

1990+
////////////////////////////////////////////////////////////////////////////////
1991+
// std::optional
1992+
////////////////////////////////////////////////////////////////////////////////
1993+
1994+
#if __cplusplus >= 201703L
1995+
namespace internal {
1996+
template <typename T>
1997+
struct BindingType<std::optional<T>> {
1998+
using ValBinding = BindingType<val>;
1999+
using WireType = ValBinding::WireType;
2000+
2001+
static WireType toWireType(std::optional<T> value) {
2002+
if (value) {
2003+
return ValBinding::toWireType(val(*value));
2004+
}
2005+
return ValBinding::toWireType(val::undefined());
2006+
}
2007+
2008+
2009+
static std::optional<T> fromWireType(WireType value) {
2010+
val optional = val::take_ownership(value);
2011+
if (optional.isUndefined()) {
2012+
return {};
2013+
}
2014+
return optional.as<T>();
2015+
}
2016+
};
2017+
} // end namespace internal
2018+
#endif
2019+
19762020

19772021
////////////////////////////////////////////////////////////////////////////////
19782022
// ENUMS

test/embind/embind.test.js

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1204,6 +1204,81 @@ module({
12041204
});
12051205
});
12061206

1207+
BaseFixture.extend("optional", function() {
1208+
if (!("embind_test_return_optional_int" in cm)) {
1209+
return;
1210+
}
1211+
test("std::optional works with returning int", function() {
1212+
var optional = cm.embind_test_return_optional_int(true);
1213+
assert.equal(42, optional);
1214+
1215+
optional = cm.embind_test_return_optional_int(false);
1216+
assert.equal(undefined, optional);
1217+
});
1218+
1219+
test("std::optional works with returning float", function() {
1220+
var optional = cm.embind_test_return_optional_float(true);
1221+
assert.equal(Math.fround(4.2), optional);
1222+
1223+
optional = cm.embind_test_return_optional_float(false);
1224+
assert.equal(undefined, optional);
1225+
});
1226+
1227+
test("std::optional works with returning SmallClass", function() {
1228+
var optional = cm.embind_test_return_optional_small_class(true);
1229+
assert.equal(7, optional.member);
1230+
optional.delete();
1231+
1232+
optional = cm.embind_test_return_optional_small_class(false);
1233+
assert.equal(undefined, optional);
1234+
});
1235+
1236+
test("std::optional works with returning string", function() {
1237+
var optional = cm.embind_test_return_optional_string(true);
1238+
assert.equal("hello", optional);
1239+
1240+
optional = cm.embind_test_return_optional_string(false);
1241+
assert.equal(undefined, optional);
1242+
});
1243+
1244+
test("std::optional works int arg", function() {
1245+
var value = cm.embind_test_optional_int_arg(42);
1246+
assert.equal(42, value);
1247+
1248+
value = cm.embind_test_optional_int_arg(undefined);
1249+
assert.equal(-1, value);
1250+
});
1251+
1252+
test("std::optional works float arg", function() {
1253+
var value = cm.embind_test_optional_float_arg(4.2);
1254+
assert.equal(Math.fround(4.2), value);
1255+
1256+
value = cm.embind_test_optional_float_arg(undefined);
1257+
assert.equal(Math.fround(-1.1), value);
1258+
});
1259+
1260+
test("std::optional works string arg", function() {
1261+
var value = cm.embind_test_optional_string_arg("hello");
1262+
assert.equal("hello", value);
1263+
1264+
value = cm.embind_test_optional_string_arg("");
1265+
assert.equal("", value);
1266+
1267+
value = cm.embind_test_optional_string_arg(undefined);
1268+
assert.equal("no value", value);
1269+
});
1270+
1271+
test("std::optional works SmallClass arg", function() {
1272+
var small = new cm.SmallClass();
1273+
var value = cm.embind_test_optional_small_class_arg(small);
1274+
assert.equal(7, value);
1275+
small.delete();
1276+
1277+
value = cm.embind_test_optional_small_class_arg(undefined);
1278+
assert.equal(-1, value);
1279+
});
1280+
});
1281+
12071282
BaseFixture.extend("functors", function() {
12081283
test("can get and call function ptrs", function() {
12091284
var ptr = cm.emval_test_get_function_ptr();

test/embind/embind_test.cpp

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@
1010
#include <emscripten/heap.h>
1111
#include <emscripten/em_asm.h>
1212

13+
#if __cplusplus >= 201703L
14+
#include <optional>
15+
#endif
16+
1317
using namespace emscripten;
1418

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

1305+
#if __cplusplus >= 201703L
1306+
std::optional<int> embind_test_return_optional_int(bool create) {
1307+
if (create) {
1308+
return 42;
1309+
}
1310+
return {};
1311+
}
1312+
std::optional<float> embind_test_return_optional_float(bool create) {
1313+
if (create) {
1314+
return 4.2;
1315+
}
1316+
return {};
1317+
}
1318+
std::optional<std::string> embind_test_return_optional_string(bool create) {
1319+
if (create) {
1320+
return "hello";
1321+
}
1322+
return {};
1323+
}
1324+
std::optional<SmallClass> embind_test_return_optional_small_class(bool create) {
1325+
if (create) {
1326+
return SmallClass();
1327+
}
1328+
return {};
1329+
}
1330+
1331+
int embind_test_optional_int_arg(std::optional<int> arg) {
1332+
if (arg) {
1333+
return *arg;
1334+
}
1335+
return -1;
1336+
}
1337+
float embind_test_optional_float_arg(std::optional<float> arg) {
1338+
if (arg) {
1339+
return *arg;
1340+
}
1341+
return -1.1;
1342+
}
1343+
std::string embind_test_optional_string_arg(std::optional<std::string> arg) {
1344+
if (arg) {
1345+
return *arg;
1346+
}
1347+
return "no value";
1348+
}
1349+
1350+
int embind_test_optional_small_class_arg(std::optional<SmallClass> arg) {
1351+
if (arg) {
1352+
return arg->member;
1353+
}
1354+
return -1;
1355+
}
1356+
#endif
1357+
13011358
val embind_test_getglobal() {
13021359
return val::global();
13031360
}
@@ -2297,6 +2354,21 @@ EMSCRIPTEN_BINDINGS(tests) {
22972354

22982355
function("test_string_with_vec", &test_string_with_vec);
22992356

2357+
#if __cplusplus >= 201703L
2358+
register_optional<int>();
2359+
register_optional<float>();
2360+
register_optional<SmallClass>();
2361+
register_optional<std::string>();
2362+
function("embind_test_return_optional_int", &embind_test_return_optional_int);
2363+
function("embind_test_return_optional_float", &embind_test_return_optional_float);
2364+
function("embind_test_return_optional_small_class", &embind_test_return_optional_small_class);
2365+
function("embind_test_return_optional_string", &embind_test_return_optional_string);
2366+
function("embind_test_optional_int_arg", &embind_test_optional_int_arg);
2367+
function("embind_test_optional_float_arg", &embind_test_optional_float_arg);
2368+
function("embind_test_optional_string_arg", &embind_test_optional_string_arg);
2369+
function("embind_test_optional_small_class_arg", &embind_test_optional_small_class_arg);
2370+
#endif
2371+
23002372
register_map<std::string, int>("StringIntMap");
23012373
function("embind_test_get_string_int_map", embind_test_get_string_int_map);
23022374

0 commit comments

Comments
 (0)