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.