无法在 std::sort 中使用 cdef 函数作为比较函数



下面的代码来自这个文件。它在 Linux 上可以正确编译,但在 OS X 上无法编译。

我想对自定义区间的向量进行排序:

stdsort(intervals.begin(), intervals.end(), compare_start_end)

我的比较函数如下:

cdef uint32_t compare_start_end(interval lhs, interval rhs):
if (lhs.start < rhs.start):
return <uint32_t> 1
elif (rhs.start < lhs.start):
return <uint32_t> 0
elif (lhs.end < rhs.end):
return <uint32_t> 1
else:
return <uint32_t> 0

我得到的错误如下:

In file included from epic2/src/read_bam.cpp:651:
/Library/Developer/CommandLineTools/usr/include/c++/v1/algorithm:4117:5: error: no matching function for call to '__sort'
__sort<_Comp_ref>(__first, __last, __comp);
^~~~~~~~~~~~~~~~~
epic2/src/read_bam.cpp:3305:12: note: in instantiation of function template specialization 'std::__1::sort<std::__1::__wrap_iter<__pyx_t_5epic2_3src_8read_bam_interval *>, unsigned int (__pyx_t_5epic2_3src_8read_bam_interval, __pyx_t_5epic2_3src_8read_bam_interval)>'
requested here
std::sort<std::vector<__pyx_t_5epic2_3src_8read_bam_interval> ::iterator,uint32_t (__pyx_t_5epic2_3src_8read_bam_interval, __pyx_t_5epic2_3src_8read_bam_interval)>(__pyx_v_intervals.begin(), __pyx_v_intervals.end(), __pyx_f_5epic2_3src_8read_bam_compare_start_end);
^
/Library/Developer/CommandLineTools/usr/include/c++/v1/algorithm:3914:1: note: candidate function template not viable: no known conversion from 'unsigned int (*)(__pyx_t_5epic2_3src_8read_bam_interval, __pyx_t_5epic2_3src_8read_bam_interval)' to 'unsigned int
(&)(__pyx_t_5epic2_3src_8read_bam_interval, __pyx_t_5epic2_3src_8read_bam_interval)' for 3rd argument; dereference the argument with *
__sort(_RandomAccessIterator __first, _RandomAccessIterator __last, _Compare __comp)
^
1 warning and 1 error generated.

问题似乎是类型之一。

我有

'unsigned int (*)(__pyx_t_5epic2_3src_8read_bam_interval, __pyx_t_5epic2_3src_8read_bam_interval)' 

但我的函数期望

unsigned int (&)(__pyx_t_5epic2_3src_8read_bam_interval, __pyx_t_5epic2_3src_8read_bam_interval)

提示是尝试取消引用第三个参数,但这不起作用。

stdsort(intervals.begin(), intervals.end(), dereference(compare_start_end))

相反,它错误

Compiling epic2/src/read_bam.pyx because it changed.
[1/1] Cythonizing epic2/src/read_bam.pyx
Error compiling Cython file:
------------------------------------------------------------
...
intervals = dereference(it).second
five_ends = intvec()
if drop_duplicates:
stdsort(intervals.begin(), intervals.end(), dereference(compare_start_end))
^

你有什么建议吗? Ps. 以上在 Linux 上编译,但不在 macOS 上编译,所以代码很脆弱。


系统信息

macOS 莫哈韦, 10.14.6 (18G87(

gcc --version
Configured with: --prefix=/Library/Developer/CommandLineTools/usr --with-gxx-include-dir=/Library/Developer/CommandLineTools/SDKs/MacOSX10.14.sdk/usr/include/c++/4.2.1
Apple LLVM version 10.0.1 (clang-1001.0.46.4)
Target: x86_64-apple-darwin18.7.0
Thread model: posix
InstalledDir: /Library/Developer/CommandLineTools/usr/bin

最小可重现示例

# minimal_example.pyx
from libc.stdint cimport uint32_t
from libcpp.algorithm cimport sort as stdsort
from libcpp.vector cimport vector
ctypedef struct interval:
uint32_t start
uint32_t end
ctypedef vector[uint32_t] intvec
ctypedef vector[interval] interval_vector

cdef uint32_t compare_start_end(interval lhs, interval rhs):
if (lhs.start < rhs.start):
return <uint32_t> 1
elif (rhs.start < lhs.start):
return <uint32_t> 0
elif (lhs.end < rhs.end):
return <uint32_t> 1
else:
return <uint32_t> 0

cdef test(interval_vector intervals):
stdsort(intervals.begin(), intervals.end(), compare_start_end)

编译方式:

folder_with_Python_h="/mnt/work/endrebak/software/anaconda/include/python3.7m/"
cython --cplus minimal_example.pyx
gcc -I $folder_with_Python_h  -c minimal_example.cpp -o minimal_example.o -Ofast -Wall -std=c++11

在macOS上弹出相同的错误消息,但不在Linux上弹出。

我尝试过的其他命令,给出相同的结果:

g++  -I /Users/endrebakkenstovner/anaconda3/include/python3.6m/ -stdlib=libc++  -c minimal_example.cpp -o minimal_example.o -Ofast -Wall
gcc  -I /Users/endrebakkenstovner/anaconda3/include/python3.6m/  -c minimal_example.cpp -o minimal_example.o -Ofast -Wall -lc++

尝试更改 Cython 代码

在 cdef 测试之前添加这些行

cdef extern from "<algorithm>" namespace "std":
void stdsort(...)

结果(在 Linux 和 macOS 上(

Error compiling Cython file:
------------------------------------------------------------
...
cdef extern from "<algorithm>" namespace "std":
void stdsort(...)

cdef test(interval_vector intervals):
stdsort(intervals.begin(), intervals.end(), compare_start_end)
^
------------------------------------------------------------
minimal_example.pyx:30:11: ambiguous overloaded method

根据注释在 stdsort(...( 的末尾添加"sort"(linux 和 macOS(:

Error compiling Cython file:
------------------------------------------------------------
...
else:
return <uint32_t> 0

cdef extern from "<algorithm>" namespace "std":
void stdsort(...) "sort"
^
------------------------------------------------------------

基本问题是 Cython 坚持指定模板参数。而不是生成如下所示的 C 代码:

std::sort(__pyx_v_intervals.begin(), __pyx_v_intervals.end(), __pyx_f_5epic2_3src_8read_bam_compare_start_end);

它生成

std::sort<std::vector<__pyx_t_5epic2_3src_8read_bam_interval> ::iterator,uint32_t (__pyx_t_5epic2_3src_8read_bam_interval, __pyx_t_5epic2_3src_8read_bam_interval)>(__pyx_v_intervals.begin(), __pyx_v_intervals.end(), __pyx_f_5epic2_3src_8read_bam_compare_start_end);

一般来说,在C++最好让C++弄清楚模板参数。在这种情况下,我认为 Cython 可能搞砸了函数指针参数。

解决方案是不要告诉Cython你有一个模板函数。这涉及自己重新包装函数,而不是使用 Cython 的 libcpp 包装器。一种选择是只指定所有类型 - 类型不必完全匹配,但必须足够接近,以便 Cython 认为它可以传递正确的参数:

cdef extern from "<algorithn>" namespace "std":
# code is untested because this isn't the solution I used...
void sort(vector[interval].iterator, vector[interval].iterator,
uint32_t (*)(interval, interval))

我更喜欢只使用....这是为了包装C varargs函数,如printf,你可以传递任何东西,但它在这里也运行得很好:

cdef extern from "<algorithm>" namespace "std":
void sort(...)
# to rename to stdsort do
void stdsort "sort"(...)

最终结果是 Cython 不再试图告诉C++模板参数应该是什么。


std::unique有点复杂,因为它具有返回类型。因此,Cython 需要知道至少一个模板参数。幸运的是,我很确定只有最后一个参数会导致问题,所以你可以放心地告诉 Cython 第一个参数是一个模板:

cdef extern from "<algorithm>" namespace "std":
Iter unique[Iter](Iter, Iter, ...)

最新更新