Skip to content

Commit 1618e63

Browse files
committed
embind: Add helper for registering a std::optional type.
1 parent 1059b20 commit 1618e63

File tree

9 files changed

+236
-3
lines changed

9 files changed

+236
-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
},

system/include/emscripten/bind.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,10 @@ void _embind_register_constant(
249249
TYPEID constantType,
250250
double value);
251251

252+
void _embind_register_optional(
253+
TYPEID optionalType,
254+
TYPEID type);
255+
252256
void _embind_register_user_type(
253257
TYPEID type,
254258
const char* typeName);
@@ -1917,6 +1921,13 @@ class_<std::vector<T>> register_vector(const char* name) {
19171921
;
19181922
}
19191923

1924+
#if __cplusplus >= 201703L
1925+
template<typename T>
1926+
void register_optional() {
1927+
internal::_embind_register_optional(internal::TypeID<std::optional<T>>::get(), internal::TypeID<T>::get());
1928+
}
1929+
#endif
1930+
19201931
////////////////////////////////////////////////////////////////////////////////
19211932
// MAPS
19221933
////////////////////////////////////////////////////////////////////////////////

system/include/emscripten/wire.h

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
#include <cstdlib>
2323
#include <memory>
2424
#include <string>
25+
#if __cplusplus >= 201703L
26+
#include <optional>
27+
#endif
2528

2629
#define EMSCRIPTEN_ALWAYS_INLINE __attribute__((always_inline))
2730

@@ -370,6 +373,31 @@ struct GenericBindingType<std::unique_ptr<T>> {
370373
}
371374
};
372375

376+
#if __cplusplus >= 201703L
377+
template <typename T>
378+
struct BindingType<std::optional<T>> {
379+
using ValBinding = BindingType<val>;
380+
using WireType = ValBinding::WireType;
381+
382+
383+
static WireType toWireType(std::optional<T> value) {
384+
if (value) {
385+
return ValBinding::toWireType(val(*value));
386+
}
387+
return ValBinding::toWireType(val::undefined());
388+
}
389+
390+
391+
static std::optional<T> fromWireType(WireType value) {
392+
val optional = val::take_ownership(value);
393+
if (optional.isUndefined()) {
394+
return {};
395+
}
396+
return optional.as<T>();
397+
}
398+
};
399+
#endif
400+
373401
template<typename Enum>
374402
struct EnumBindingType {
375403
typedef Enum WireType;

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

test/other/embind_tsgen.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include <stdio.h>
22
#include <memory>
33
#include <string>
4+
#include <optional>
45
#include <emscripten/bind.h>
56

67
using namespace emscripten;
@@ -97,6 +98,10 @@ std::wstring wstring_test(std::wstring arg) {
9798
return L"hi";
9899
}
99100

101+
std::optional<int> optional_test(std::optional<Foo> arg) {
102+
return {};
103+
}
104+
100105
class BaseClass {
101106
public:
102107
virtual ~BaseClass() = default;
@@ -164,6 +169,10 @@ EMSCRIPTEN_BINDINGS(Test) {
164169

165170
function("global_fn", &global_fn);
166171

172+
register_optional<int>();
173+
register_optional<Foo>();
174+
function("optional_test", &optional_test);
175+
167176
function("string_test", &string_test);
168177
function("wstring_test", &wstring_test);
169178

test/other/embind_tsgen.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ export interface MainModule {
8888
a_bool: boolean;
8989
an_int: number;
9090
global_fn(_0: number, _1: number): number;
91+
optional_test(_0: Foo | undefined): number | undefined;
9192
smart_ptr_function(_0: ClassWithSmartPtrConstructor): number;
9293
smart_ptr_function_with_params(foo: ClassWithSmartPtrConstructor): number;
9394
function_with_callback_param(_0: (message: string) => void): number;

0 commit comments

Comments
 (0)