Experimental next-gen binding framework for Node.js / Node-API inspired by pybind11
Inspired by pybind11
and embind
, in turn inspired by the groundbreaking Boost.Python
.
This framework is designed around C++17 fold expressions.
It has one defining characteristic that sets it apart from pybind11
and embind
- every wrapper is statically generated at compile time and has no run-time state. All the state information is constexpr
and it is encoded in the template parameters. The wrappers are instantiated by obtaining a pointer to the wrapper function.
This allows for both a (slightly) better performance and code simplicity.
The unit tests run on:
- g++ 9.4 on Linux (the default compiler on Ubuntu 20.04)
- clang 13 on macOS (the default compiler on macOS 11)
- MSVC 19.29 on Windows (Visual Studio 16.11 aka 2019)
However because of edge cases when it comes to C++17 support, the recommended compiler versions are:
- g++ 10.5 on Linux (the alternative choice on Ubuntu 20.04 and the default one on Ubuntu 22.04)
- clang 13 on macOS (the default compiler on macOS 11)
- MSVC 19.37 on Windows (Visual Studio 17.7 aka 2022)
It is meant as an easy to use entry-level light-weight binding framework for simple projects that target only Node.js.
Complex projects should continue to use SWIG which is cross-platform and cross-language.
Currently, the project should be considered of a recent release quality.
The first npm
module to use it is @mmomtchev/ffmpeg
, you can check it for advanced usage examples.
A future compatible layer should allow to target both embind
and nobind17
with shared declarations.
Full pybind11
compatibility is also a very long term goal - allowing a module to support both Node.js and Python.
You can use nobind-example-project
and hadron-nobind-example-project
as a template for creating a new nobind17
based project using node-gyp
or hadron
as build system.
Feature | SWIG Node-API | nobind17 |
---|---|---|
Design goal | Create bindings for (almost) any C++ code with (almost) native feel | Easy to use, easy to learn |
Target use | Commercial-grade bindings for large C++ libraries | Very fast porting of C++ code with few methods/classes |
Method of operation | Custom C++ header compiler, uses its own interface language, generates C++ code | Collection of C++ templates to be included in the project |
Method of using | Must write metaprogramming code | Must enumerate the binded methods using C++ syntax |
C++ requirements | C++11 | C++17 with some features such as wrapping of lambdas requiring C++20 |
C++ types | Almost all, nested classes support is very limited | No functions pointers, no nested classes, enum s are not automatic |
C++ preprocessing integration | Yes, can expose macros to JS | No |
C++ namespaces | Can be exposed to JS with some limitations and manual work | Supported in C++ but not exposed to JS |
C++ iterators | manual | automatic |
Buffer s / ArrayBuffer s / TypedArray s |
Yes | Only Buffer s for now |
STL | Complete, supports both JS using C++ STLs without copying and C++ using JS types with copying | Limited, all passing of STL arguments is by copying |
Async | Automatic | Automatic |
Async locking | Yes, with automatic dead-lock prevention | Not at the moment, but planned |
Smart pointers | Yes | Not at the moment, but planned |
TypeScript support | Automatic | Automatic |
ES6 named exports for all C/C++ functions | Yes, automatic | No, must write it |
WASM/Browser support | Yes | Not at the moment, but planned through embind compatibility |
Cross-platform | Yes | Yes |
Input language | Both C and C++ | Mostly C++, many usual C API semantics are not well supported |
Target language | Most dynamic languages | An eventual abstraction layer between nobind17 , embind and pybind11 is planned in theory |
Exposing C++ inheritance to JavaScript | Yes, automatic with implicit downcasting support, diamond inheritance is not supported | Yes, but no automatic downcasting support and no diamond inheritance |
Overloading | Yes | Only for constructors, overloaded methods must be renamed to be usable in JS |
Optional arguments with default values | Yes, automatic | No, all arguments become mandatory |
Complex argument transformations (for example C++ expects (char**, size_t* ) as input argument, JS expects Buffer as returned type) |
Yes | Only 1 :1 and 1 :0 transformations of input arguments |
Custom type casters | Yes | Yes |
Interfacing between multiple modules | Yes | No |
nobind17
is a set of C++17 templates that must be included directly in the user project.
It is published as an npm package that will also install node-addon-api
.
Starting from Node.js 18, C++17 is the default build mode for both Node.js itself and for addons. Unless you set manually NAPI_VERSION
in your project, [email protected]
will default to NAPI_VERSION=9
which requires Node.js 18.17, 20.3 or 21 and later. Older versions use NAPI_VERSION-6
which allows backward compatibility of the generated binary addon with Node.js 14 and later - even when using Node.js 18 as the build platform.
nobind17
is designed to be very easy to use - there is no learning curve at all - while allowing to deal with the most common situations that arise when creating bindings for C++ libraries to be used from Node.js.
The following tutorial should be enough to get you started with your C++ project.
You can also check node-ffmpeg as an example for a large project using nobind17
.
Create a a binding.gyp
, then create a package.json
for your project and install nobind17
:
binding.gyp
{
'target_defaults': {
'includes': [
# These are the correct compiler options
# to enable C++ exceptions with node-gyp
'except.gypi'
]
},
'targets': [
{
'target_name': 'my-shiny-cpp-bindings',
'sources': [
# This is the file that contains your bindings
# (from the tutorial below)
'src/my-shiny-cpp-bindings.cc'
# List your C++ files here
# If you have a large library, check
# https://github.com/mmomtchev/node-ffmpeg
# for inspiration, it builds ffmpeg with conan
],
'include_dirs': [
'<!@(node -p "require(\'node-addon-api\').include")',
'<!@(node -p "require(\'nobind17\').include")'
]
}
]
}
npm init # ... answer questions
npm install nobind17
cp node_modules/node-addon-api/except.gypi .
You will be building your project with node-gyp configure build
. node-gyp
is usually installed globally.
C++17 is the default build mode starting from Node.js 18.x. If you
Let's try to wrap a simple C++ class:
class Hello {
public:
std::string name;
Hello(const std::string &s) : name(s) {}
std::string Greet(const std::string &s) {
std::stringstream r;
r << "hello " << s << " " << name_;
return r.str();
}
};
Start by creating a module:
// nobind will automatically include napi.h and it will
// define NAPI_VERSION and NAPI_EXPERIMENTAL
// If you include it yourself without these, you will be
// missing the synchronous finalizers
#include <nobind.h>
// Define a new module
NOBIND_MODULE(my_cpp_bindings, m) {
// Expose a C++ class called Hello
m.def<Hello>("Hello")
// Include a constructor with a single const std::string & argument
.cons<const std::string &>();
}
nobind17
supports global methods and instance and static class methods. All of them are declared by using .def()
:
// Expose a global function global_fn
m.def<&global_fn>("global_fn");
m.def<MyClass>("Hello")
.cons<std::string &>()
// Expose a class method (whether it is static or instance)
.def(&Hello::Greet, "greet");
nobind17
will identify the type of the class method, static methods will be available through the class itself and instance methods will be available through the object instance.
A class can have multiple constructors, including a default one (use <>
for its arguments). The number of arguments on the JavaScript side determine which one will be used. If there a multiple constructors expecting the same number of arguments, they will be tried in the order of their declaration - the first one which is able to convert its arguments will win.
Overloaded methods, other than constructors, must be explicitly resolved and each signature must have a different name in JavaScript.
Arguments will be automatically converted. The C++ type of the wrapped function selects the type converter. The basic types supported out of the box are:
JavaScript type | C++ type |
---|---|
number |
int , short , long , unsigned , unsigned short , unsigned long , long long , unsigned long long , double , float |
string |
std::string , char * |
boolean |
bool |
object |
std::map<string, T> (all properties must have the same type) |
Array |
std::vector<T> (all items must have the same type) |
instances of class registered to nobind17 |
class object, pointers and references |
Buffer |
std::pair<uint8_t *, size_t> |
A raw V8 Napi::Value |
Napi::Value |
Additional custom type converters can be registered by the user.
Global as well as class static and instance variables can be exposed with the same type conversion rules:
// Expose a read-only global variable version
m.def<&version, Nobind::ReadOnly>("version");
m.def<MyClass>("Hello")
.cons<std::string &>()
// Expose a class instance variable with getter and setter
.def(&Hello::name, "name");
nobind17
will automatically determine if the object is a static or an instance one.
Using STLs usually requires creating a wrapper function unless the original C++ function has been designed from the ground up to work with nobind17
:
// A function that receives a JS array of Hello objects
// It calls the .Greet() method of each object
// and returns a JS array of strings
std::vector<std::string>
GreetAll(const std::string &title, const std::vector<Hello *> &array) {
std::vector<std::string> r;
r.reserve(array.size());
for (auto obj : array) {
r.push_back(obj->Greet(title));
}
return r;
}
NOBIND_MODULE(array, m) {
m.def<&GreetAll>("greetAll");
m.def<MyClass>("Hello")
// Include a constructor with a single std::string & argument
.cons<std::string &>()
.def<&Hello::Greet>("greet");
}
Used from JavaScript this function will have the following semantics:
const output = dll.greetAll('Mr', [
new dll.Hello('Brown'),
new dll.Hello('Orange'),
new dll.Hello('Pink')
]);
typeof output[0] === 'string'
std::vector
can be of any supported type - including known registered object types, pointers or references to them, primitives types or any other additional custom type. nobind17
will take care to transform the pointers and the references to JS objects.
Methods that raise a C++ exception will result in a normal JavaScript exception in the JavaScript code.
Building with C++ exceptions enabled is mandatory.
Methods can be made to run in a background thread from the libuv
thread pool and to return a Promise
to be resolved with the returned value:
m.def<Hello>("Hello")
.def<&Hello::Greet, Nobind::ReturnAsync>("greetAsync");
Everything is fully automatic. Raising a C++ exception will reject the Promise
.
Enabling async mode will allow the JS user to potentially call the C++ method while a previous invocation is still running. If the C++ method is not fully reentrant, a wrapper with a lock mechanism should be implemented.
By default, when a C++ method returns a nullptr
, nobind17
will convert it to null
in JavaScript. This behavior can be overridden by specifying Nobind::ReturnNullThrow
as a return attribute - in this case the method will throw. If the method is asynchronous, it will reject.
Attributes can be combined with operator|
, however if compiling in C++17 mode (the default settings for node-gyp
), only static constexpr
variables can be used as non-type template arguments:
static constexpr auto myAttrs = Nobind::ReturnAsync | Nobind::ReturnOwned | Nobind::ReturnNullThrow;
In later standards this requirement has been relaxed. Also, MSVC 2019 chokes on static constexpr
local function variables used as non-type template arguments with an C1001: Internal Compiler Error - use global variables if you have to support it.
Custom type converters can be declared as follows:
// This example overrides the default `int` typemaps
// with typemaps that expect and return strings
// Start by including this file
#include <nooverrides.h>
namespace Nobind {
// Typemaps that will be overriding built-ins must live
// in this namespace to override
// (typemaps for new types must be in Nobind::Typemap)
namespace TypemapOverrides {
// They consist of two simple classes templated on the C++ type
// (the C++ type is the determning type)
// This one will be called whenever nobind17 needs to convert
// a JS argument to C++ int
template <> class FromJS<int> {
int val_;
public:
// The first part will be called from the V8 context
// It must import the value and store it so that it can
// be accessed without V8
// It must check if the JS argument is of the correct type
inline explicit FromJS(Napi::Value val): Inputs(1) {
if (!val.IsString()) {
throw Napi::TypeError::New(val.Env(), "Expected a string");
}
val_ = std::atoi(val.ToString().Utf8Value().c_str());
}
// The second part may be called from a background thread
// It should Expected access V8
inline int Get() { return val_; }
// An optional public member may specify the number
// of consumed JS arguments (considered 1 if not present)
int Inputs;
// Optionally, if the typemap has a costly state, only move
// semantics may be specified, nobind17 can work with this type
FromJS(const FromJS &) = delete;
FromJS(FromJS &&) = default;
};
// This typemap will be used when C++ returns an int
// It must create a value for JS
template <> class ToJS<int> {
Napi::Env env_;
int val_;
public:
// The first part may be called from a background thread
// It should simply store the value for later use
inline explicit ToJS(Napi::Env env, int val) : env_(env), val_(val) {}
// The second part will be called on the main V8 thread
// It should produce a JS value
inline Napi::Value Get() { return Napi::String::New(env_, std::to_string(val_)); }
// Optionally, if the typemap has a costly state, only move
// semantics may be specified, nobind17 can work with this type
ToJS(const ToJS &) = delete;
ToJS(ToJS &&) = default;
};
} // namespace TypemapOverrides
} // namespace Nobind
#include <nobind.h>
int add(int a, int b) {
return a + b;
}
NOBIND_MODULE(override_tmaps, m) {
m.def<&add>("add");
}
Note that nooverrides.h
must be included first, then the custom typemaps, then the rest of the templates with nobind.h
.
A very good starting point for implementing a custom typemap are the standard number typemaps in nonumbermaps.h
, the string ones in nostringmaps.h
and the STL maps which are recursive in nostl.h
.
Unless the C++ code has been designed for nobind17
, using a Buffer
will likely require creating custom wrappers to convert from and to std::pair<uint8_t*, size_t>
:
#include <fixtures/buffer.h>
// Nobind::Buffer is defined as follows:
// using Buffer = std::pair<uint8_t *, size_t>;
#include <nobind.h>
// These are the underlying C++ functions that use buffers
// We want to call them from JS
void get_buffer(uint8_t *&, size_t &);
void put_buffer(uint8_t *, size_t);
// These wrappers are what makes them nobind17-compatible
Nobind::Typemap::Buffer nobind_get_buffer() {
Nobind::Typemap::Buffer buf;
get_buffer(buf.first, buf.second);
return buf;
}
void nobind_put_buffer(Nobind::Typemap::Buffer buf) {
put_buffer(buf.first, buf.second);
}
NOBIND_MODULE(buffer, m) {
m.def<&nobind_get_buffer>("get_buffer")
.def<&nobind_put_buffer>("put_buffer");
}
When C++ returns a Buffer
object, that buffer is considered owned and it will be freed upon the destruction of the Node.js Buffer
object by the garbage-collector.
When JavaScript passes a Buffer
to a C++ method, C++ receives a pointer to the underlying data region of the JS Buffer
which is protected from the GC for duration of the call - including in async mode.
Before continuing with this section, we should explain the notion of a JS proxy.
Each C++ object is created with new
and destroyed with delete
in the C++ heap. These objects are not directly visible from JavaScript. What is visible from JavaScript is called a JS proxy - a pure JS object that contains a hidden pointer to the underlying C++ object. This JS object is managed by the V8 GC.
This means that functions that return C++ objects need to be compatible with the GC rules in JavaScript. For every function, other than a constructor, that returns an object, there must be clear rules on who frees the C++ object.
By default, nobind17
will consider that it owns objects returned as pointers and that it does not own objects returned as references. This behavior can be modified with an attribute:
class Chained {
public:
Chained();
Chained *Factory();
Chained &Do();
};
NOBIND_MODULE(chained, m) {
m.def<Chained>("Chained")
.cons<>()
// Nobind::ReturnOwned is the default behavior for pointers
.def<&Chained::Factory, Nobind::ReturnOwned>("create");
// Nobind::ReturnShared is the default behavior for references
.def<&Chained::Do, Nobind::ReturnShared>("do");
}
.do()
is a method that can be chained:
const o = new Chained;
o.do().do().do();
The Nobind::ReturnShared
signals nobind17
that C++ objects returned by this method should not be considered new objects and should not be freed when the JS proxy is collected by the GC.
.create()
is a method that creates new objects. The Nobind::ReturnOwned
signals nobind17
that C++ objects returned by this method should be considered new objects and should be freed when the GC destroys the JS proxy.
Also, be sure to check #1 for a very important warning about shared references and also read the section on nested references below.
Eventually, as last resort, Nobind::ReturnCopy
will copy the returned object. This might not be very efficient, but it will always be safe. The copy will be destroyed when the returned reference is GCed. Nobind::ReturnCopy
works only for objects. Plain objects are always copied anyway, but it also allows to copy objects returned as references or pointers.
Sometimes it is very handy to be able to add an additional class method in JavaScript that does not directly correspond to a C++ method. For example, the standard way of providing a method returning a readable string representation of an object is to overload the global operator<<
. In JavaScript, the standard method is to replace the Object.toString()
. This cannot be achieved with a simple helper function, because it will have to be a member of the binded class. In this case nobind17
allows to define a special function of the form RETTYPE Method(CLASS &, ARGS...)
and to register it as a class extension:
std::string HelloToString(const Hello &);
m.def<Hello>("Hello").ext<&ToString>("toString");
The first argument of the class extension must be const CLASS &
, CLASS &
or Napi::Value
- it will contain the this
object.
Currently, there is no way to register a getter with a function in order to override the [@@toStringTag]
property.
C++ functions that expect Napi::Value
arguments or return Napi::Value
results will skip the type conversions. This can be used to interact directly with the underlying Node.js API.
Unlike raw Node-API, C++ functions will receive their Napi::Value
s with the usual C++ convention:
Napi::Value add(Napi::Value a, Napi::Value b);
Mixing is also supported:
int add(Napi::Value a, int b);
In this case only the first argument will contain the raw V8 value.
It is also possible to access the exports
and env
objects when initializing the module:
constexpr bool False = false;
NOBIND_MODULE(native, m) {
m.Exports().Set("debug_build", Napi::Boolean::New(m.Env(), true));
m.def<Hello>("Hello")
.def<&False, Nobind::ReadOnly>(Napi::Symbol::WellKnown(m.Env(), "isConcatSpreadable"));
}
Automatically generating Napi::Env
arguments without consuming input arguments is also possible, this allows to create a function without any arguments that returns a Napi::Value
:
Napi::Value get_string(Napi::Env env) {
return Napi::String::New(env, "string w/o arguments");
}
All Napi::Env
arguments will be automatically filled.
Consider the following C++ code:
class Time {
unsigned long timestamp;
public:
Time(unsigned long v): timestamp(v) {};
};
class DateTime {
Time time;
public:
DateTime(Time v): time(v) {};
Time &get() { return time; };
};
DateTime
can returned a (non-const
) reference to its member object Time
. This reference should obviously use shared semantics as the newly created JS proxy object won't own the underlying C++ object. However, what will happen if the GC collects the parent object while JavaScript is still holding a reference to the returned nested object? This special case, which is somewhat common in the C++ world, requires special handling that can be enabled by using the Nobind::ReturnNested
return attribute. In this case the returned reference will be bound the parent object which will be protected from the GC until the nested reference exists. This return attribute has a meaning only for class members and it is applied by default for class getters.
Typemaps can use recursion to reference other typemaps - this typemap for std::map<std::string, T>
calls the existing typemaps for each contained object by using FromJSValue<T>
and ToJSValue<T>
.
template <typename T> class FromJS<std::map<std::string, T>> {
std::map<std::string, T> val_;
public:
inline explicit FromJS(const Napi::Value &val) {
if (!val.IsObject()) {
throw Napi::TypeError::New(val.Env(), "Expected an object");
}
Napi::Object object = val.ToObject();
for (auto prop : object) {
val_.insert({prop.first.ToString().Utf8Value(), FromJSValue<T>(prop.second).Get()});
}
}
inline M Get() { return val_; }
FromJS(const FromJS &) = delete;
FromJS(FromJS &&) = default;
};
Sometimes a module needs to store "global" data. With node-addon-api
the proper way to store this data is in a per-isolate data structure - since Node.js is allowed to call the same instance from multiple independent isolates. To access the per-isolate storage with nobind17
, declare the module specific structure and then use the standard node-addon-api
calls to access it:
struct PerIsolateData {
Napi::ObjectReference exports;
};
NOBIND_MODULE_DATA(native, m, PerIsolateData) {
m.Env().GetInstanceData<Nobind::EnvInstanceData<PerIsolateData>>()->exports =
Napi::Persistent<Napi::Object>(m.Exports());
}
nobind17
/ node-addon-api
will take care of creating and freeing this structure when new isolates are created and destroyed.
Direct simple inheritance without virtual methods (almost) works out of the box. In order to properly set up the JavaScript instanceof
operator, the class definitions must include the base class as a second template argument:
m.def<Derived, Base>("Derived");
The only caveat is that this does not automatically inherit all the base class members. These must be declared separately for each class:
m.def<Base>("Base").cons<int>().def<&Base::get>("get").def<&Base::base_get>("base_get");
m.def<Derived, Base>("Derived").cons<int>().def<&Derived::get>("get").def<&Derived::base_get>("base_get");
In this case, get()
is a virtual method overriden in Derived
and there is a single base_get()
in Base
that must also be explicitly declared in Derived
. Resolution of virtual methods is left to the C++ compiler and follows the usual rules.
MSVC 2019, which is not fully C++17 compliant, requires a static_cast
in this situation: see here. Later versions are fully compliant when using /permissive-
.
When having to transpose multiple inheritance in C++ to JavaScript, it is possible to declare multiple implemented interfaces:
m.def<Derived, Base, Interface1, Interface2>("Derived");
Currently this has an effect only on the TypeScript definitions which will include the corresponding implements
declarations. instanceof
in JavaScript will work only with the first base class.
Version 2 adds support for built-in automatically generated TypeScript definitions. These will be available inside the binary module in a special read-only variable called __typescript
. This behaviour can be disabled if the module is built with the macro NOBIND_NO_TYPESCRIPT_GENERATOR
defined. The default property name can be modified by defining NOBIND_TYPESCRIPT_PROP
. In order to generate custom types, custom typemaps must have an additional method called TSType()
returning an std::string
with the TypeScript type:
template <> class FromJS<bool> {
bool val_;
public:
inline explicit FromJS(const Napi::Value &val) {
if (!val.IsBoolean()) {
throw Napi::TypeError::New(val.Env(), "Expected a boolean");
}
val_ = val.ToBoolean().Value();
}
inline bool Get() { return val_; }
static const std::string TSType() { return "boolean"; };
};
If the compiler supports RTTI and has the demangling ABI, defining NOBIND_TYPESCRIPT_DEBUG
will produce type annotation comments with the original C++ types.
Generating TypeScript definitions may require the use of forward declarations - for example when two classes reference each other. In this case at least one of the classes must be declared before the other one is defined:
m.decl<Base>("Base");
m.def<Dependant>("Dependant").cons<const Hello &>();
m.def<Base>("Base").cons<std::string &>();
The declaration and definition must use the same name. This allows the TypeScript generator to be able to correctly resolve Base
objects when generating Dependant
.
When creating generic typemaps, the current TypeScript name of the type can be obtained by calling NoObjectWrap<T>::GetName()
- this requires that the class has at least been previously declared.
static const std::string &TSType() { return NoObjectWrap<T>::::GetName(); };
Recursive typemaps with TypeScript support can use the FromTSType<T>
and ToTSType<T, RETATTR = ReturnNullThrow>
typemaps to obtain the TypeScript definitions of the nested objects. Additionnaly, createTSRecord<T, U>
and createTSArray<T>
can be used to create Record<>
and []
definitions.
Inserting a custom TypeScript code fragment anywhere at the root level in the code is possible with:
m.typescript_fragment("export class CustomClass {}");
Iterators are mostly automatic but you must be aware that C++ iterators return references to the objects inside the container. The ownership of these objects is not always clear, but generally they are considered to be owned by the container.
nobind17
offers two built-in interfaces to deal with iterable objects - one that copîes the returned objects to JavaScript and another one which returns shared references which prevent the container to be destroyed until the last returned object has been destroyed.
To define an iterator for the C++ class Iterable
, instantiate the built-in JSIterator
classe specifying either ReturnCopy
or ReturnNested
to define a JS-compatible iterator that has a next
method. For TypeScript support it should implement Nobind::TSIterator<Iterable>
- this will automatically define its TypeScript to return whatever type the C++ iterator returns - which will be Iterable::iterator::value_type
as per the C++17 specifications:
m.def<Nobind::JSIterator<Iterable, Nobind::ReturnCopy>, void, Nobind::TSIterator<Iterable>>(
"_nobind_iterable_copy_iterator")
.def<&Nobind::JSIterator<Iterable, Nobind::ReturnCopy>::next>("next");
Then in the definition of the Iterable
class add the built-in helper MakeJSIterator
as a Symbol.iterator
extension method for the class:
m.def<Iterable, void, Nobind::TSIterable<Iterable>>("Iterable")
.ext<&Nobind::MakeJSIterator<Iterable1, Nobind::ReturnCopy>>(Napi::Symbol::WellKnown(m.Env(), "iterator"));
There is no ReturnDefault
when working with iterators to further stress the fact that there is a decision to make.
Besides ReturnNested
and ReturnCopy
- which cover most cases, there are also cases where ReturnShared
and ReturnOwned
might be needed, but these are not completely safe and you should use them only if you understand the implications. ReturnShared
means that the container does not own the returned object and it should never be freed - this is often the case for containers of pointers or containers of static objects. ReturnOwned
is the most unusual - this means that the iterator is transfering the responsability of the returned object to its caller.
This expects that Iterable
implements std::input_iterator_tag
which is the most basic C++17 iterator - implementing only the pointer advancement operation and the indirection.
There is an example in iterator.cc
.
nobind17
does not support R-value references. These cannot really be expressed in JavaScript because a C++ method that expects an R-value reference will have to destroy the passed value in the parent scope - something that cannot be expressed in JavaScript.
Most of the work that nobind17
does happens during the C++ compilation of the project. It is at that moment that the templates will be instantiated.
As it is often the case with C++ compilation, the errors may be hard to read.
When encountering compilation errors, start with this quick checklist:
-
Does the error message mention missing typemaps such as
FromJS
/ToJS
?You are trying to expose types that
nobind17
does not know how to convert, you need a custom typemap. -
Is the method that does not compile an overloaded method?
You need to use
static_cast
to manually resolve the overloading. -
Is the method that does not compile inherited from a base class?
You need to use the base class name.
-
Is the custom typemap not being picked up?
Custom typemaps must be included before
nobind.h
but afternooverrides.h
.When overriding the builtin typemaps, you must use the special
Nobind::TypemapOverrides
namespace.Other typemaps must be in
Nobind::Typemap
.Depending on your types, you may need to also include pointer, reference or
const
typemaps - check the built-in implementation ofstd::string
for an example. -
Are you using MSVC?
MSVC has a number of problems with template argument deduction in its default compilation mode. The
/permissive-
and/Zc
flags can help in some cases, or you can also use astatic_cast
to explicitly type your function pointer.node-ffmpeg
includes a few cases of this type.Also, MSVC 2019 has a number of problems such as C1001: Internal Compiler Error on
static constexpr
local function variables used as non-type template arguments and some complex SFINAE constructs such as this one: MSVC fails to specialize template withstd::enable_if
and a non-type argument. -
assert(class_idx == 0 || class_idx == idx)
failsYou most probably have multiple definitions for the same class.
Although building to WASM using emnapi
should be possible, this is considered out of scope for this project and you should be using embind
which implements the same functionality directly in the emscripten
compiler without adding additional layers (C++/nobind
to node-addon-api
, then node-addon-api
/emnapi
to embind
).
Running single unit tests (in a debugger) is possible by doing:
cd test
node single configure <test>
node single build
node single run