Skip to content

Commit

Permalink
Support for argument names, keyword arguments and argument default va…
Browse files Browse the repository at this point in the history
…lues (#142)

* extra_arg: add jlcxx::arg and jlcxx::kwarg

* extra_arg: nicer lambda detection

* extra_arg: add arg info to FunctionWrapper

* extra_arg: check argument counts

* extra_arg: improve argument passing

* extra_arg: working arg.-names and kw-args

* extra_arg: first test with default arg

* extra_arg: fix bug from moving code around

* extra_arg: avoid error with msvc
  • Loading branch information
melven authored Jan 13, 2024
1 parent c244461 commit 24906be
Show file tree
Hide file tree
Showing 6 changed files with 340 additions and 56 deletions.
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ set(JLCXX_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src)

set(JLCXX_HEADERS
${JLCXX_INCLUDE_DIR}/jlcxx/array.hpp
${JLCXX_INCLUDE_DIR}/jlcxx/attr.hpp
${JLCXX_INCLUDE_DIR}/jlcxx/const_array.hpp
${JLCXX_INCLUDE_DIR}/jlcxx/jlcxx.hpp
${JLCXX_INCLUDE_DIR}/jlcxx/jlcxx_config.hpp
Expand Down
9 changes: 8 additions & 1 deletion examples/functions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -202,12 +202,19 @@ JLCXX_MODULE init_test_module(jlcxx::Module& mod)
mod.method("boxednumber_nb_deleted", [] () { return BoxedNumber::m_nb_deleted; });

mod.method("concatenate_numbers", &concatenate_numbers);
mod.method("concatenate_numbers_with_named_args", &concatenate_numbers, jlcxx::arg("i"), jlcxx::arg("d"));
// wrong number of arguments doesn't compile
// mod.method("concatenate_numbers_with_kwargs", &concatenate_numbers, jlcxx::kwarg("i"));
mod.method("concatenate_numbers_with_kwargs", &concatenate_numbers, jlcxx::kwarg("i"), jlcxx::kwarg("d"));
mod.method("concatenate_numbers_with_default_values", &concatenate_numbers, jlcxx::arg("i"), jlcxx::arg("d")=5.2);
mod.method("concatenate_numbers_with_default_values_of_different_type", &concatenate_numbers, jlcxx::arg("i"), jlcxx::arg("d")=5);
mod.method("concatenate_numbers_with_default_kwarg", &concatenate_numbers, jlcxx::arg("i"), jlcxx::kwarg("d")=5.2);
mod.method("concatenate_strings", &concatenate_strings);
mod.method("test_int32_array", test_int32_array);
mod.method("test_int64_array", test_int64_array);
mod.method("test_float_array", test_float_array);
mod.method("test_double_array", test_double_array);
mod.method("test_exception", test_exception, "", true);
mod.method("test_exception", test_exception, jlcxx::calling_policy::std_function);
mod.method("test_array_len", test_array_len);
mod.method("test_array_set", test_array_set);
mod.method("test_array_get", test_array_get);
Expand Down
2 changes: 1 addition & 1 deletion examples/types.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ JLCXX_MODULE define_julia_module(jlcxx::Module& types)

types.add_type<World>("World")
.constructor<const std::string&>()
.constructor<jlcxx::cxxint_t>(false) // no finalizer
.constructor<jlcxx::cxxint_t>(jlcxx::finalize_policy::no) // no finalizer
.constructor([] (const std::string& a, const std::string& b) { return new World(a + " " + b); })
.method("set", &World::set)
.method("greet_cref", &World::greet)
Expand Down
194 changes: 194 additions & 0 deletions include/jlcxx/attr.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
#ifndef JLCXX_ATTR_HPP
#define JLCXX_ATTR_HPP

#include <string>
#include <vector>
#include <type_traits>
#include <functional>

#include "jlcxx_config.hpp"
#include "type_conversion.hpp"


// This header provides internal helper functionality for providing additional information like argument names and default arguments for C++ functions (method in module.hpp)

namespace jlcxx
{

namespace detail
{
/// Helper type for function arguments
template <bool IsKwArg>
struct JLCXX_API BasicArg
{
static constexpr bool isKeywordArgument = IsKwArg;

const char *name = nullptr;
jl_value_t* defaultValue = nullptr;

BasicArg(const char *name_) : name(name_) {}

template <typename T>
inline BasicArg &operator=(T value)
{
defaultValue = box<T>(std::forward<T>(value));
return *this;
}
};
}

/// use jlcxx::arg("argumentName") to add function argument names, and jlcxx::arg("name")=value to define an argument with a default value
using arg = detail::BasicArg<false>;

///! use jlcxx::kwarg("argumentName") to define a keyword argument and with jlcxx::kwarg("name")=value you can add a default value for the argument
using kwarg = detail::BasicArg<true>;

/// enum for the force_convert parameter for raw function pointers
enum class calling_policy : bool
{
ccall = false,
std_function = true
};
/// default value for the calling_policy argument for Module::method with raw C++ function pointers
constexpr auto default_calling_policy = calling_policy::ccall;

/// enum for finalize parameter for constructors
enum class finalize_policy : bool
{
no = false,
yes = true
};
/// default value for the finalize_policy argument for Module::constructor
constexpr auto default_finalize_policy = finalize_policy::yes;


namespace detail
{
/// SFINEA for argument processing, inspired/copied from pybind11 code (pybind11/attr.h)
template<typename T, typename SFINEA = void>
struct process_attribute;

/// helper type for parsing argument and docstrings
struct ExtraFunctionData
{
std::vector<arg> positionalArguments;
std::vector<kwarg> keywordArguments;
std::string doc;
calling_policy force_convert = default_calling_policy;
finalize_policy finalize = default_finalize_policy;

};

/// process docstring
template<>
struct process_attribute<const char*>
{
static inline void init(const char* s, ExtraFunctionData& f)
{
f.doc = s;
}
};

template<>
struct process_attribute<char*> : public process_attribute<const char*> {};

/// process positional argument
template<>
struct process_attribute<arg>
{
static inline void init(arg&& a, ExtraFunctionData& f)
{
f.positionalArguments.emplace_back(std::move(a));
}
};

/// process keyword argument
template<>
struct process_attribute<kwarg>
{
static inline void init(kwarg&& a, ExtraFunctionData& f)
{
f.keywordArguments.emplace_back(std::move(a));
}
};

/// process calling_policy argument
template<>
struct process_attribute<calling_policy>
{
static inline void init(calling_policy force_convert, ExtraFunctionData& f)
{
f.force_convert = force_convert;
}
};

/// process finalize_policy argument
template<>
struct process_attribute<finalize_policy>
{
static inline void init(finalize_policy finalize, ExtraFunctionData& f)
{
f.finalize = finalize;
}
};

template<typename T>
void parse_attributes_helper(ExtraFunctionData& f, T argi)
{
using T_ = typename std::decay_t<T>;
process_attribute<T>::init(std::forward<T_>(argi), f);
}

/// initialize ExtraFunctionData from argument list
template<bool AllowCallingPolicy = false, bool AllowFinalizePolicy = false, typename... Extra>
ExtraFunctionData parse_attributes(Extra... extra)
{
// check that the calling_policy is only set if explicitly allowed
constexpr bool contains_calling_policy = (std::is_same_v<calling_policy, Extra> || ... );
static_assert( (!contains_calling_policy) || AllowCallingPolicy, "calling_policy can only be set for raw function pointers!");

// chat that the finalize_policy is only set if explicitly allowed
constexpr bool contains_finalize_policy = (std::is_same_v<finalize_policy, Extra> || ... );
static_assert( (!contains_finalize_policy) || AllowFinalizePolicy, "finalize_policy can only be set for constructors!");

ExtraFunctionData result;

(parse_attributes_helper(result, std::move(extra)), ...);

return result;
}

/// count occurences of specific type in parameter pack
template<typename T, typename... Extra>
constexpr int count_attributes()
{
return (0 + ... + int(std::is_same_v<T,Extra>));
}

static_assert(count_attributes<float, int, float, int, double>() == 1);
static_assert(count_attributes<int, int, float, int, double, int, int>() == 4);

/// check number of arguments matches annotated arguments if annotations for keyword arguments are present
template<typename... Extra>
constexpr bool check_extra_argument_count(int n_arg)
{
// with keyword arguments, the number of annotated arguments must match the number of actual arguments
constexpr auto n_extra_arg = count_attributes<arg, Extra...>();
constexpr auto n_extra_kwarg = count_attributes<kwarg, Extra...>();
return n_extra_kwarg == 0 || n_arg == n_extra_arg + n_extra_kwarg;
}

/// simple helper for checking if a template argument has a call operator (e.g. is a lambda)
template<class T, typename SFINEA = void>
struct has_call_operator : std::false_type {};

template<class T>
struct has_call_operator<T, std::void_t<decltype(&T::operator())>> : std::true_type {};

static_assert(!has_call_operator<const char*>::value);
static_assert(has_call_operator<std::function<void()>>::value);
}

}

#endif
Loading

0 comments on commit 24906be

Please sign in to comment.