Is it possible (and if so how) to use node-addon-api with C++ code that is templated. That is rather than specifying the types of the arguments as in:
int add(int a, int b) {
return a + b;
}
the type is templated as in
template <typename T>
T add(T a, T b) {
return a + b;
}
I have followed the excellent tutorial at:
Beginners guide to writing Node-JS addons using C, N-API and node-addon-api.
I have implemented the examples given and tried to extend them to a templated example, but when I run the compilation I get errors. I don't claim to be a templated C++ expert, I have inherited templated C++ code and I want to make it available in Javascript.
I have uploaded the examples I have created to a repository on GitHub: napi-addon-templated.
Any help on this would be greatly appreciated, even just a pointer to a worked example would be good. I understand that templating makes it hard to wrap and understand that there might be a need to have a case statement that handles each type correctly, again, even a simple examp,e of how to do that would be beneficial.
Many thanks for your help.
Hi @Graar,
this is a pure C++ question and has nothing to do with node-addon-api.
Of course you can use templates. Keep in mind, that templates are like macros: they may produce code which will be compiled and after compilation they are gone. In your .node file there will be no templates anymore. It's just something the language supports for writing code.
Read about C++ templates.
@Graar Is it possible close this issue or you need some more information?
@NickNaso Yes, please can we keep it open. I'm trying to catch-up after the holidays and I want to give a considered response to @DaAitch. I don't want this to be my reply, but what I am struggling with is where the type conversion occurs on input.
On the input side, as @DaAitch observes, after compilation in straight C++, the templated class would be replaced with versions of the class for each type used. However, when you are wrappering C++ for use as a library in a different language, then (I think) you need some way of specifying the type of the data that is being sent and directing that to the correct class/method. You also need to ensure there is a compiled implementation for each of the types that you will support.
I don't claim to be a templated C++ expert, but I don't see how the underlying C++ can know what type it will receive. In wrappers for other languages, I have seen people use an additional "dtype" argument to specify the kind of data being sent and then in the wrapper call the correct version of the class/method.
So, in the example I used above, "add" could operate on int32 or on strings. If we were to call that method from Javascript, then we would need to make sure that the code called the correct compiled implementation to handle the input data type, and I don't see how that can all happen at the C++ layer.
If there are any comments to this, then that's greatly appreciated. Otherwise, I'll try and put something more concrete in place to illustrate the issue I'm having.
@Graar
However, when you are wrappering C++ for use as a library in a different language, then (I think) you need some way of specifying the type of the data that is being sent and directing that to the correct class/method.
Good question, but that's not what is happening with napi or node-addon-api. We only export one initialize method, to be called from nodejs to setup our module with exports. The interfaces we are implementing are
napi_value myfunc_c(napi_env env, napi_callback_info info); // editedNapi::Value MyFuncCpp(const Napi::CallbackInfo& info);If you want to support JS numbers and strings you need have some checks in you impl, whether a number or string is given and then put correct impl, like this (not tested):
template <typename T>
T add(const T& a, const T& b) {
return a + b;
}
Napi::Value Add(const Napi::CallbackInfo& info) {
if (info.Length() != 2) {
Napi::TypeError(info.Env(), Napi::String::New(info.Env(), "2 parameters expected"))
.ThrowAsJavaScriptException();
return Napi::Value();
}
if (info[0].IsString() && info[1].IsString()) {
// template called: add<std::string> will be implemented
return Napi::String::From(info.Env(), add<std::string>(info[0].ToString(), info[1].ToString()));
}
if (info[0].IsNumber() && info[1].IsNumber()) {
// template called: add<double> will be implemented
return Napi::Number::New(info.Env(), add(info[0].ToNumber().DoubleValue(), info[1].ToNumber().DoubleValue()));
}
Napi::TypeError(info.Env(), Napi::String::New(info.Env(), "only numbers and strings can be added"))
.ThrowAsJavaScriptException();
return Napi::Value();
}
You also need to ensure there is a compiled implementation for each of the types that you will support.
Yes, but simply go through your code. If a template isn't used with a specific type, then it will not be compiled with it.
I don't claim to be a templated C++ expert, but I don't see how the underlying C++ can know what type it will receive. In wrappers for other languages, I have seen people use an additional "dtype" argument to specify the kind of data being sent and then in the wrapper call the correct version of the class/method.
What do you mean by other languages? We only have JS and C/C++ in this context. Your .node binary will not export any functions with generic types.
If there are any comments to this, then that's greatly appreciated. Otherwise, I'll try and put something more concrete in place to illustrate the issue I'm having.
Please provide a small example for more specific questions.
@Graar were you looking for something like the example below?
// example.cc
#include <napi.h>
template <typename T>
T add(T l, T r) {
return l + r;
}
Napi::Value Add(const Napi::CallbackInfo& info) {
if (info[0].IsNumber() && info[1].IsNumber()) {
double l = info[0].As<Napi::Number>();
double r = info[1].As<Napi::Number>();
return Napi::Number::New(info.Env(), add(l, r));
} else if (info[0].IsString() && info[1].IsString()) {
std::string l = info[0].As<Napi::String>();
std::string r = info[1].As<Napi::String>();
return Napi::String::New(info.Env(), add(l, r));
} else {
Napi::Error::New(info.Env(), "Unhandled type of addition")
.ThrowAsJavaScriptException();
return info.Env().Undefined();
}
}
Napi::Object Init(Napi::Env env, Napi::Object exports) {
return Napi::Function::New(env, Add, "add");
}
NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init)
// example.js
const add = require('./build/Release/example');
console.log('add(5, 6): ' + add(5, 6));
console.log('add(\'five\', \'six\'): ' + add('five', 'six'));
console.log('add(\'five\', 6): ' + add('five', 6));
Woah! /me needs to refresh before commenting 馃檪 I provided almost exactly the same example as @DaAitch 馃檪
@DaAitch wrote:
@Graar
The interfaces we are implementing are* napi: `napi_value myfunc_c(napi_callback_info info);`
Nit: for N-API it's napi_value myfunc_c(napi_env env, napi_callback_info info);
:laughing: yeah @Graar now you have two examples.
@gabrielschulhof thanks, did update it in my comment
@Graar
However, when you are wrappering C++ for use as a library in a different language, then (I think) you need some way of specifying the type of the data that is being sent and directing that to the correct class/method.
Are you creating a wrapper to create native add-ons with programing language different from C / C++? If yes I'm interested in which were your implementation choices, obviously if you can and want talk about it.
@DaAitch @gabrielschulhof @NickNaso
Thanks so much guys. I see now and I understand the code above.
Just to clear up the confusion I created about other languages, we have wrappered the same C++ code using SWIG to expose a Python API, so when I said wrappers for other languages I meant wrappers for the C++ code to make it available to other languages, specifically Python. Currently, I am not trying to do anything other then wrap C++ for use as a Javascript library.
Again, many thanks.
@Graar sounds like it would be ok to close this issue now?
@mhdawson
Yes absolutely. Thanks very much.
Closing the issue.