std::optional<> 的 SWIG Python 包装器

SWIG Python wrapper for std::optional<>

我正在编写一个 C++ 17 库,以及一个带有 SWIG (4.0.2) 的 python 3 包装器。库中的一些函数 return 是 std::optional<> 类型。我正在通过 SWIG 为 python 寻找 C++17 的 std::optional<T> 的包装器,但我找不到任何可以帮助我处理 T 的在线内容,一个实际类型(这个 Ttemplate<typename T> 中的 T 不同,它是 class 的名称)。这是我试过的方法,但没有用。

  1. 我已经设法在定义两个函数的 std::optional<> 中检索结果:
%inline %{
bool is_optional_valid(const std::optional<T>& o) { return (o ? true : false); }
T get_data(const std::optional<T>& o) { return *o; }
%}

在 python 中,我使用这些函数是这样的:

opt = ... # call to C++ function that returns an std::optional<>
if is_optional_valid(opt):
    contents = get_graph(opt)
    del opt
    return contents

del opt
return None

但这 return 是(可能是预期的)错误:

swig/python detected a memory leak of type 'std::optional< T > *', no destructor found.
  1. 使用
%template(optional_T) std::optional<T>;

无效,因为 SWIG return 错误:

Error: Template 'optional' undefined.
  1. 我一直在查看官方文档中的类型映射,但无法从中得出结论。而且我什至不确定这些是否适用于我的情况。例如,此 github 存储库包含 std_optional.i 用于 float
%typemap(in) std::optional<float> %{
    if($input == Py_None) {
         = std::optional<float>();
    }
    else {
         = std::optional<float>((float)PyFloat_AsDouble($input));
    }
%}
%typemap(out) std::optional<float> %{
    if() {
        $result = PyFloat_FromDouble(*);
    }
    else {
        $result = Py_None;
        Py_INCREF(Py_None);
    }
%}

但我不确定我是否可以使用类似的东西来包装 T(class 的名称)。我试过了,但我不知道要在 ????.

中放什么
%typemap(in) std::optional<T> %{
    if($input == Py_None) {
         = std::optional<T>();
    }
    else {
         = std::optional<T>(T($input));
    }
%}
%typemap(out) std::optional<lal::graphs::directed_graph> %{
    if() {
        $result = ????(*);
    }
    else {
        $result = Py_None;
        Py_INCREF(Py_None);
    }
%}

问题 我如何用 SWIG(对于 Python)包装 std::optional<> 对于 T 其中 T 是class 的名称(如果可能的话)?我可以使用上面给出的方法制作自己的模板 class。像这样:

template<typename T>
class optional_wrapper {
    std::optional<T> m_optional_member;
public:
    // constructors...
    bool has_contents() const noexcept { return (m_optional_member ? true : false); }
    T get_contents() const noexcept { return *m_optional_member; }
};

最后,

namespace my_library {
%template(optional_wrapper_T) optional_wrapper<T>;
}

但我认为这不是 C++ 库的最佳实践,因为我将包装现有类型,而包装类型没有任何新内容(请告诉我您是否同意)。

如有任何帮助,我们将不胜感激。谢谢。

第一种方法如下。假设您在命名空间 library.

中有一个名为 call_to_function 的函数,其参数为 ARGS(这可能是 int, intstd::string

首先,为std::optional<T>定义并实现一个模板包装器:

%{
template<class T>
struct LB__optional {
    std::optional<T> m_opt;
    bool has_contents() const noexcept { return (m_opt ? true : false); }
    T contents() const noexcept { return *m_opt; }
};
%}

返回 std::optional<> 的函数应调用:

// note that this does not compile
template<class T>
LB__optional<T> call_to_function(ARGS...) {
    __optional<T> r;
    r.m_opt = library::call_to_function(ARGS...);
    return r;
}

包装 LB__optionalcall_to_function:

/* ---------- WRAP __optional ------------- */

template<class T>
struct LB__optional {
    std::optional<T> m_opt;
    bool has_contents() const noexcept { return (m_opt ? true : false); }
    T contents() const noexcept { return *m_opt; }
};

/* ---------- WRAP call_to_function ------------- */

template<class T> LB__optional<T> call_to_function(ARGS...);

/* ---------- INSTANTIATE LB__optional FOR SEVERAL TYPES ------------- */

%template(LB__optional_T) LB__optional<T>;

/* ---------- INSTANTIATE call_to_function FOR SEVERAL TYPES ------------- */

%template(call_to_function_T) call_to_function<T>;

添加一些python代码:

%pythoncode %{

def call_to_function(instantiated_type, ARGS...):
    r"""
    Document your function please

    Parameters:
    ----------
    * `instantiated_type` :
        A type for which `call_to_function` has been instantiated in the `.i` file
    * `ARGS` :
        Not actual parameter, but rather a shorthand for the parameters that should be passed to `call_to_function`.
    """
    
    if instantiated_type not in [TYPES FOR WHICH ]:
        return None
    
    opt = globals()[ "call_to_function_" + instantiated_type ](ARGS...)
    # we now have access to the "optional" because it is a type that
    # python knows about since SWIG wrapped it (a wrapper for __optional)
    if opt.has_contents(): return opt.contents()
    return None
%}