Reputation: 30684
I'm attempting to tidy up some code.
I have 16 classes, all of which share some common functionality, which I have abstracted using a macro:
#define COMMON4( CLASS, BASE, ASSIGN, CHECK ) \
explicit CLASS( PyObject* pyob, bool owned=false ) \
: BASE{ pyob, owned } { \
validate(); } \
\
CLASS& operator=( const Object& rhs ) { \
return *this = rhs.ptr(); } \
\
CLASS& operator=( PyObject* rhsp ) { \
if(ptr()!=rhsp) set(ASSIGN); return *this; } \
\
bool accepts( PyObject* pyob ) const override { \
return pyob && CHECK; }
#define COMMON5( CLASS, BASE, CHECK ) \
COMMON4( CLASS, BASE, rhsp, CHECK ) \
CLASS( const Object& ob ) : BASE{ ob.ptr() } { \
validate(); } \
// Class Type
class Type: public Object
{
public:
COMMON5( Type, Object, _Type_Check(pyob) )
Type( const Type& t ) : Object{t} { validate(); }
};
// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
class Boolean: public Object
{
public:
COMMON5( Boolean, Object, PyObject_IsTrue(pyob) != -1 )
Boolean( const Boolean& ob ) : Object{ob} { validate(); }
Boolean( bool v=false ) { set( PyBool_FromLong(v?1:0), true ); validate(); } // create from bool
Boolean& operator=( bool v ) { set( PyBool_FromLong(v?1:0), true ); return *this; }
operator bool() const { return as_bool(); }
};
// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
class Long: public Object
{
public:
COMMON4( Long, Object, PyNumber_Long(rhsp), _Long_Check(pyob) )
// π I *think* the explicit was originally on the wrong function below
explicit Long( const Long& ob ) : Object{ ob } { validate(); }
Long( const Object& ob ) : Object{ PyNumber_Long(ob.ptr()) , true } { validate(); } // ... any object
:
}
: (etc)
However, I have just discovered that C++11 supports constructor inheritance, e.g.:
#include <string>
#include <iostream>
using namespace std;
class B {
public:
B() { cout << "B:-\n"; }
B(int i) { cout << "B:int\n"; }
B(float i) { cout << "B:float\n"; }
};
class D : public B {
using B::B;
public:
D(double i) : B() { cout << "D:double\n"; }
};
class D2 : public B {
using B::B;
public:
D2(float i) : B() { cout << "D:float\n"; }
};
class D3 : public B {
using B::B;
public:
D3(float i) = delete;
};
int main() {
D a{5}; // B:int
D b{5.f}; // B:float
D c{5.}; // B:- D:double
D2 d{5.f}; // B:- D:float
//D3 e{5.f}; // error: use of deleted function 'D3::D3(float)'
}
This code shows it is possible to fall back onto base class constructors, while simultaneously offering the opportunity to override them.
That's exactly what I need, as occasionally a class breaks the pattern slightly.
This is the rewrite:
using CheckType = bool(PyObject*);
inline PyObject* setDefault(PyObject* pyob){ return pyob; };
template< typename Derived, typename Base, CheckType checkfunc, decltype(setDefault) setfunc = setDefault >
class ObjBase : public Object
{
public:
ObjBase(){ };
explicit ObjBase( PyObject* pyob, bool owned=false ) : Base{pyob,owned} { validate(); }
Derived& operator=( const Object& rhs ) { return *this = rhs.ptr(); }
Derived& operator=( PyObject* rhsp ) { if(ptr()!=rhsp) set(setfunc(rhsp)); return *this; }
bool accepts( PyObject* pyob ) const override { return pyob && checkfunc(pyob); }
ObjBase( const Object& ob ) : Base{ob.ptr()} { validate(); }
};
// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
// Class Type
class Type: public ObjBase< Type, Object, _Type_Check > {
using ObjBase< Type, Object, _Type_Check >::ObjBase;
using ObjBase< Type, Object, _Type_Check >::operator=;
public:
// COMMON5( Type, Object, _Type_Check(pyob) )
Type( const Type& t ) : ObjBase{t} { validate(); }
};
// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
inline bool bool_check( PyObject* pyob ){ return PyObject_IsTrue(pyob) != -1; }
class Boolean: public ObjBase< Boolean, Object, bool_check > // <-- 1. Candidate is the implicit copy assignment operator
{
using ObjBase< Boolean, Object, bool_check >::ObjBase;
using ObjBase< Boolean, Object, bool_check >::operator=;
public:
// COMMON5( Boolean, Object, PyObject_IsTrue(pyob) != -1 )
Boolean( const Boolean& ob ) : ObjBase{ob} { validate(); }
Boolean( bool v=false ) { set( PyBool_FromLong(v?1:0), true ); validate(); } // create from bool
Boolean& operator=( bool v ) { set( PyBool_FromLong(v?1:0), true ); return *this; } // <-- 2. Candidate function
operator bool() const { return as_bool(); }
};
:
: (etc)
The first class works -- Type. However, the Boolean class causes a compiler error when I attempt to make use of this class.
ERROR: Use of overloaded operator '=' is ambiguous (with operand types 'Py::Boolean' and 'Py::Long')
Boolean b{true};
Long l{15};
b = l; // <-- ERROR: Use of overloaded operator '=' is ambiguous (with operand types 'Py::Boolean' and 'Py::Long')
The error specifies the two candidates, which I have labelled in the code:
// Candidate function
Boolean& operator=( bool v ) { set( PyBool_FromLong(v?1:0), true ); return *this; }
But the other points to the line of the class declaration itself:
// Candidate is the implicit copy assignment operator
class Boolean: public ObjBase< Boolean, Object, bool_check > // <-- ?!
{ ...
And it is this I don't understand.
My first guess is that there is some conflict with the assignment operator in the base class.
However, the error is specifically saying "Candidate is the implicit copy assignment operator"
Looking up "implicit copy assignment operator", http://en.cppreference.com/w/cpp/language/as_operator
... which says that an implicit copy assignment is generated, but it is deleted if certain criteria are met.
My guess is that the original class met these criteria. But the act of moving most of the methods back into a base class maybe means that none of these criteria are now met.
However, I can't see it.
How to push this one forwards?
EDIT: complete source file listing here
EDIT: I've noticed also that by moving the operator= overloads back into ObjBase, '*this' is now incorrect. Replacing it with 'static_cast(*this)' then gives an additional errors.
Upvotes: 0
Views: 166
Reputation: 171303
The second candidate is probably the implicitly-defined copy-assignment operator, which is not shown in the source code so the compiler points to the class itself.
Your Long
class has an implicit conversion to various integer types that are convertible to bool
and those types are also convertible to Boolean
, and both of those types can be assigned to a Boolean
, so the compiler doesn't know which to convert to before performing the assignment.
You could make the conversion explicit so the compiler doesn't need to check:
b = bool(l);
or equivalently, but more explicitly (and maybe more clearly for some readers):
b = static_cast<bool>(l);
You probably want to avoid too many explicit conversions between types, it gets confusing and leads to these sort of problems. To disable implicit conversion and require explicit casts to tell the compiler what conversions you want to perform, either make converting constructors explicit
, or make the conversion operators explicit
(or both).
Upvotes: 2