So I have an existing library that provides a string type.
It implicitly converts to-from C style strings like so:
struct TypeIDoNotOwn {
TypeIDoNotOwn() {}
TypeIDoNotOwn(TypeIDoNotOwn const&) {}
TypeIDoNotOwn(char const*) {}
TypeIDoNotOwn& operator=(TypeIDoNotOwn const&) {return *this;}
TypeIDoNotOwn& operator=(char const*) {return *this;}
operator char const*() const {return nullptr;}
};
it has other methods, but I do not think they are important. These methods have bodies, but my problem doesn't involve them, so I have stubbed them out.
What I want to do is to create a new type that can be used relatively interchangably with the above type, and with "raw string constants"
. I want to be able to take an instance of TypeIDoNotOwn
, and replace it with TypeIDoOwn
, and have code compile.
As an example, this set of operations:
void test( TypeIDoNotOwn const& x ) {}
int main() {
TypeIOwn a = TypeIDoNotOwn();
TypeIDoNotOwn b;
a = b;
b = a;
TypeIOwn c = "hello";
TypeIDoNotOwn d = c;
a = "world";
d = "world";
char const* e = a;
std::pair<TypeIDoNotOwn, TypeIDoNotOwn> f = std::make_pair( TypeIOwn(), TypeIOwn() );
std::pair<TypeIOwn, TypeIOwn> g = std::make_pair( TypeIDoNotOwn(), TypeIDoNotOwn() );
test(a);
}
If I replace TypeIOwn
with TypeIDoNotOwn
above, it compiles. How do I get it to compile with TypeIOwn
without modifying TypeIDoNotOwn
? And without having to introduce any casts or changes other than the change-of-type at point of declaration?
My first attempt looks somewhat like this:
struct TypeIOwn {
TypeIOwn() {}
operator char const*() const {return nullptr;}
operator TypeIDoNotOwn() const {return {};}
TypeIOwn( TypeIOwn const& ) {}
TypeIOwn( char const* ) {}
TypeIOwn( TypeIDoNotOwn const& ) {}
TypeIOwn& operator=( char const* ) {return *this;}
TypeIOwn& operator=( TypeIOwn const& ) {return *this;}
TypeIOwn& operator=( TypeIDoNotOwn const& ) {return *this;}
};
but I get a series of ambiguous overloads:
main.cpp:31:4: error: use of overloaded operator '=' is ambiguous (with operand types 'TypeIDoNotOwn' and 'TypeIOwn') b = a; ~ ^ ~ main.cpp:9:17: note: candidate function TypeIDoNotOwn& operator=(TypeIDoNotOwn const&) {return *this;} ^ main.cpp:10:17: note: candidate function TypeIDoNotOwn& operator=(char const*) {return *this;}
and
/usr/include/c++/v1/utility:315:15: error: call to constructor of 'TypeIDoNotOwn' is ambiguous : first(_VSTD::forward<_U1>(__p.first)), ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ main.cpp:40:51: note: in instantiation of function template specialization 'std::__1::pair<TypeIDoNotOwn, TypeIDoNotOwn>::pair<TypeIOwn, TypeIOwn>' requested here std::pair<TypeIDoNotOwn, TypeIDoNotOwn> f = std::make_pair( TypeIOwn(), TypeIOwn() ); ^ main.cpp:7:7: note: candidate constructor TypeIDoNotOwn(TypeIDoNotOwn const&) {} ^ main.cpp:8:7: note: candidate constructor TypeIDoNotOwn(char const*) {} ^
In my "real" code I have other operators, like +=
and ==
, that have similar problems.
The scope of the real problem is large; millions of lines of code, and I want to swap out TypeIDoNotOwn for TypeIOwn at many thousands of locations, but not at many hundreds of others. And at thousands of locations they interact in a way that causes the conversion ambiguity.
I have solved the problem of a function taking TypeIDoNotOwn&
at the 100s of spots where it happens by wrapping it with a macro that creates a temporary object that creates a TypeIDoNotOwn
from the TypeIOwn
, returns a reference to that, then when the temporary object is destroyed copies it back to the TypeIOwn
. I want to avoid having to do a similar sweep to handle ==
, +=
, =
, copy-construction, and similar situations.
If I try to remove the operator TypeIDoNotOwn
to clear up that ambiguity, other cases where the conversion need occur don't work right (as it requires 2 user-defined constructions to get from TypeIOwn
to TypeIDoNotOwn
), which then requires an explicit conversion to occur (at many 100s or 1000s of locations)
If I could make one conversion look worse than the other, it would work. Failing that, I could try fixing the non-operator=
and copy-construct cases by overloading a free TypeIDoNotOwn == TypeIOwn
operator with exact matching (and similar for other cases), but that doesn't get me construction, function calls, and assignment.
main
here) or the type you do not own, this is not going to fly .. too bad we don't have free (non member) user defined conversion operators; with them one could probably make use of ADL to resolve that ambiguity. – Pliablechar const (&)[SIZE]
doesn't work (decays the same for initialization andTypeIDoNotOwn
construction). – Pliablestruct TypeIDoOwn : TypeIDoNotOwn
? – Pony