Skip to content

Commit 1602b56

Browse files
committed
[cppyy] Disallow conversion from C++ instance to function pointers
Implicit conversion from C++ instances to function pointers is not allowed in C++, and it can result in confusing overload resolutions if we allow it in cppyy. See: #20687 (comment)
1 parent d82f047 commit 1602b56

File tree

3 files changed

+58
-5
lines changed

3 files changed

+58
-5
lines changed

bindings/pyroot/cppyy/CPyCppyy/src/Converters.cxx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2655,7 +2655,7 @@ static PyMethodDef gWrapperCacheEraserMethodDef = {
26552655
};
26562656

26572657
static void* PyFunction_AsCPointer(PyObject* pyobject,
2658-
const std::string& rettype, const std::string& signature)
2658+
const std::string& rettype, const std::string& signature, bool allowCppInstance)
26592659
{
26602660
// Convert a bound C++ function pointer or callable python object to a C-style
26612661
// function pointer. The former is direct, the latter involves a JIT-ed wrapper.
@@ -2702,8 +2702,12 @@ static void* PyFunction_AsCPointer(PyObject* pyobject,
27022702
return fptr;
27032703
}
27042704

2705-
if (PyCallable_Check(pyobject)) {
2705+
2706+
if (PyCallable_Check(pyobject) && (allowCppInstance || !CPPInstance_Check(pyobject))) {
27062707
// generic python callable: create a C++ wrapper function
2708+
// Sometimes we don't want to take this branch if the object is a C++
2709+
// instance, because C++ doesn't allow converting functor obejcts to
2710+
// function pointers, but only to std::function.
27072711
void* wpraddress = nullptr;
27082712

27092713
// re-use existing wrapper if possible
@@ -2808,7 +2812,7 @@ bool CPyCppyy::FunctionPointerConverter::SetArg(
28082812
}
28092813

28102814
// normal case, get a function pointer
2811-
void* fptr = PyFunction_AsCPointer(pyobject, fRetType, fSignature);
2815+
void* fptr = PyFunction_AsCPointer(pyobject, fRetType, fSignature, fAllowCppInstance);
28122816
if (fptr) {
28132817
SetLifeLine(ctxt->fPyContext, pyobject, (intptr_t)this);
28142818
para.fValue.fVoidp = fptr;
@@ -2840,7 +2844,7 @@ bool CPyCppyy::FunctionPointerConverter::ToMemory(
28402844
}
28412845

28422846
// normal case, get a function pointer
2843-
void* fptr = PyFunction_AsCPointer(pyobject, fRetType, fSignature);
2847+
void* fptr = PyFunction_AsCPointer(pyobject, fRetType, fSignature, fAllowCppInstance);
28442848
if (fptr) {
28452849
SetLifeLine(ctxt, pyobject, (intptr_t)address);
28462850
*((void**)address) = fptr;

bindings/pyroot/cppyy/CPyCppyy/src/DeclareConverters.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -408,13 +408,16 @@ class FunctionPointerConverter : public Converter {
408408
protected:
409409
std::string fRetType;
410410
std::string fSignature;
411+
bool fAllowCppInstance = false;
411412
};
412413

413414
// std::function
414415
class StdFunctionConverter : public FunctionPointerConverter {
415416
public:
416417
StdFunctionConverter(Converter* cnv, const std::string& ret, const std::string& sig) :
417-
FunctionPointerConverter(ret, sig), fConverter(cnv) {}
418+
FunctionPointerConverter(ret, sig), fConverter(cnv) {
419+
fAllowCppInstance = true;
420+
}
418421
StdFunctionConverter(const StdFunctionConverter&) = delete;
419422
StdFunctionConverter& operator=(const StdFunctionConverter&) = delete;
420423
virtual ~StdFunctionConverter() { delete fConverter; }

bindings/pyroot/cppyy/cppyy/test/test_overloads.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,5 +405,51 @@ class Derived : public Base {
405405

406406
assert result_instance == result_direct
407407

408+
409+
def test14_disallow_functor_to_function_pointer(self):
410+
"""Make sure we're no allowing to convert C++ functors to funciton
411+
pointers, extending the C++ language in an unnatural way that can lead
412+
to wrong overload resolutions."""
413+
414+
import cppyy
415+
416+
cppyy.cppdef("""
417+
class Test14Functor {
418+
public:
419+
double operator () (double* args, double*) {
420+
return 4.0 * args[0];
421+
}
422+
};
423+
424+
int test14_foo(double (*fcn)(double*, double*)) {
425+
return 0;
426+
}
427+
428+
template<class T>
429+
int test14_foo(T fcn) {
430+
return 1;
431+
}
432+
433+
int test14_bar(double (*fcn)(double*, double*)) {
434+
return 0;
435+
}
436+
437+
int test14_baz(double (*fcn)(double*, double*)) {
438+
return 0;
439+
}
440+
441+
int test14_baz(std::function<double(double*, double*)> const &fcn) {
442+
return 2;
443+
}
444+
""")
445+
446+
functor = cppyy.gbl.Test14Functor()
447+
assert cppyy.gbl.test14_foo(functor) == 1 # should resolve to foo(T fcn)
448+
# not allowed, because there is only an overload taking a function pointer
449+
raises(TypeError, cppyy.gbl.test14_bar, functor)
450+
# The "baz" function has a std::function overload, which should be selected
451+
assert cppyy.gbl.test14_baz(functor) == 2 # should resolve to baz(std::function)
452+
453+
408454
if __name__ == "__main__":
409455
exit(pytest.main(args=['-sv', '-ra', __file__]))

0 commit comments

Comments
 (0)