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

Template conversions to c++ types #681

Closed
bergkvist opened this issue Mar 9, 2020 · 5 comments
Closed

Template conversions to c++ types #681

bergkvist opened this issue Mar 9, 2020 · 5 comments
Labels

Comments

@bergkvist
Copy link

bergkvist commented Mar 9, 2020

Right now, conversion from JavaScript to C++-types are not very template-friendly.

Consider the following:

std::string a = x.As<Napi::String>().Utf8Value();
std::u16string b = x.As<Napi::String>().Utf16Value();
int32_t c = x.As<Napi::Number>().Int32Value();
int64_t d = x.As<Napi::Number>().Int64Value();
float e = x.As<Napi::Number>().FloatValue();
double f = x.As<Napi::Number>().DoubleValue();

I propose something like the following (as an equivalent way of doing the same thing):

std::string a = x.Value<std::string>();
std::u16string b = x.Value<std::u16string>();
int32_t c = x.Value<int32_t>();
int64_t d = x.Value<int64_t>();
float e = x.Value<float>();
double f = x.Value<double>();

Advantages of this approach:

This would allow for useful utility templates/functions like:

template<typename T>
T get_argument(const Napi::CallbackInfo &info, int i) {
  if (i >= info.Length()) {
    std::string message = "Argument at position " + std::to_string(i) + " required.";
    throw Napi::Error::New(info.Env(), message);
  }
  return info[i].Value<T>();
}

template<typename T>
T get_argument(const Napi::CallbackInfo &info, int i, T defaultValue) {
  if (i >= info.Length()) return defaultValue;
  return info[i].Value<T>();
}

// Then I could do:
// See https://github.com/nodejs/node-addon-api/issues/680
Napi::Number add(const Napi::CallbackInfo &info) {
  double x = get_argument<double>(info, 0);
  double y = get_argument<double>(info, 1, 3.0);
  return Napi::Number::New(info.Env(), x + y);
}

Which would be equivalent to:

// JavaScript
const add = (x, y = 3) => x + y

Alternatively, in case it becomes unclear when the .As<T>() conversion is implicit:

std::string a = x.As<Napi::String>().Value<std::string>();
std::u16string b = x.As<Napi::String>().Value<std::u16string>();
int32_t c = x.As<Napi::Number>().Value<int32_t>();
int64_t d = x.As<Napi::Number>().Value<int64_t>();
float e = x.As<Napi::Number>().Value<float>();
double f = x.As<Napi::Number>().Value<double>();
@mhdawson
Copy link
Member

It was pointed out in the N-API meeting this week that something similar was discussed in: #639.

@mhdawson
Copy link
Member

@bergkvist can you take a look and see if the module referenced in #639 meets what you were asking for.

@bergkvist
Copy link
Author

bergkvist commented Mar 17, 2020

I ended up solving it like this:

#include <string>
#include <type_traits>

#define CPP_VALUE(return_type, statement)                      \
  template <typename specified_return_type>                    \
  typename std::enable_if_t<                                   \
      std::is_same<specified_return_type, return_type>::value, \
      specified_return_type>                                   \
  cpp_value(const Napi::Value value) {                         \
    return statement;                                          \
  }
CPP_VALUE(std::string, value.As<Napi::String>().Utf8Value())
CPP_VALUE(std::u16string, value.As<Napi::String>().Utf16Value())
CPP_VALUE(int32_t, value.As<Napi::Number>().Int32Value())
CPP_VALUE(int64_t, value.As<Napi::Number>().Int64Value())
CPP_VALUE(uint32_t, value.As<Napi::Number>().Uint32Value())
CPP_VALUE(float, value.As<Napi::Number>().FloatValue())
CPP_VALUE(double, value.As<Napi::Number>().DoubleValue())
CPP_VALUE(uint32_t *, value.As<Napi::Uint32Array>().Data())
CPP_VALUE(uint16_t *, value.As<Napi::Uint16Array>().Data())
CPP_VALUE(uint8_t *, value.As<Napi::Uint8Array>().Data())
CPP_VALUE(int32_t *, value.As<Napi::Int32Array>().Data())
CPP_VALUE(int16_t *, value.As<Napi::Int16Array>().Data())
CPP_VALUE(int8_t *, value.As<Napi::Int8Array>().Data())

Which enables me to do:

Napi::Number add(const Napi::CallbackInfo &info) {
  auto x = cpp_value<double>(info[0]);
  auto y = cpp_value<double>(info[1]);
  return Napi::Number::New(info.Env(), x + y);
}

This means it is now possible for me to write type-independent logic with templates for parsing arguments. It is also slightly less verbose.


@mhdawson: #639 looks very interesting, and very much in line with what I'm trying to do here. There is one potential problem I'm seeing with it - which is to allow for accepting/destructuring a JavaScript Object as an argument.

To extend my implementation above to allow for accepting a JavaScript object, it would be possible to do something like this:

struct ObjectWrapper {
  Napi::Object object;
  ObjectWrapper(Napi::Object object) : object{object} {}
  template <typename T>
  T get(std::string key);
};

CPP_VALUE(ObjectWrapper, ObjectWrapper{value.As<Napi::Object>()})

template <typename T>
T ObjectWrapper::get(std::string key) {
  return cpp_value(this->object.Get(key));
}

Which allows for the following:

// Can be called as add({ x: 3, y: 4 }) === 7 in Node.js
Napi::Number add(const Napi::CallbackInfo &info) {
  const args = cpp_value<ObjectWrapper>(info[0]);
  auto x = args.get<double>("x");
  auto y = args.get<double>("y");
  return Napi::Number::New(info.Env(), x + y);
}

@mhdawson
Copy link
Member

@bergkvist without having had time to dive into the details, is it possible to contribute a PR to #639 that would add your solution for accepting/destructuring a JavaScript Object as an argument.

@github-actions
Copy link
Contributor

This issue is stale because it has been open many days with no activity. It will be closed soon unless the stale label is removed or a comment is made.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants